From 4f0d85fa8cdfa4322cb19a54bd1e8a35d8b1b2ad Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 13 Dec 2016 14:08:04 -0300 Subject: [PATCH 001/355] Add targetCompatibility to 1.7 --- lib/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/build.gradle b/lib/build.gradle index e6942415..4d2a5c38 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -30,6 +30,7 @@ auth0 { compileJava { sourceCompatibility '1.7' + targetCompatibility '1.7' } dependencies { From c9bb9e37b78c13fe49ed5a0411108f1716122a07 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 13 Dec 2016 14:31:57 -0300 Subject: [PATCH 002/355] Make travis run only on master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index afa24235..c66eeaf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,4 @@ after_failure: - cat $HOME/travis/build/auth0/java-jwt/lib/build/reports/tests/index.html branches: only: - - v3 \ No newline at end of file + - master \ No newline at end of file From 725cd896eabe83443a91787771665a8b98afc1a6 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 13 Dec 2016 15:27:13 -0300 Subject: [PATCH 003/355] Make release script to update maven dep in README --- scripts/release.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/release.gradle b/scripts/release.gradle index 8c8ef3c7..da74e9b5 100644 --- a/scripts/release.gradle +++ b/scripts/release.gradle @@ -100,13 +100,16 @@ class ReadmeTask extends DefaultTask { @TaskAction def update() { def file = new File(filename) - def updated = "compile '${project.group}:${project.name}:${next}'" + def gradleUpdated = "compile '${project.group}:${project.name}:${next}'" def oldSingleQuote = "compile '${project.group}:${project.name}:${current}'" def oldDoubleQuote = "compile \"${project.group}:${project.name}:${current}\"" + def mavenUpdated = "${next}" + def mavenOld = "${current}" def contents = file.getText('UTF-8') - contents = contents.replace(oldSingleQuote, updated).replace(oldDoubleQuote, updated) + contents = contents.replace(oldSingleQuote, gradleUpdated).replace(oldDoubleQuote, gradleUpdated).replace(mavenOld, mavenUpdated) file.write(contents, 'UTF-8') } + } class ReleasePlugin implements Plugin { From 1f8449df26d92be7aedd72254c3e6317db0db802 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 13 Dec 2016 15:28:08 -0300 Subject: [PATCH 004/355] Release 3.0.2 --- CHANGELOG.md | 8 +++++++- README.md | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 758b7b55..69bd568c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.0.2](https://github.com/auth0/java-jwt/tree/3.0.2) (2016-12-13) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.0.1...3.0.2) + +**Fixed** +- Add targetCompatibility to 1.7 [\#121](https://github.com/auth0/java-jwt/pull/121) ([hzalaz](https://github.com/hzalaz)) + ## [3.0.1](https://github.com/auth0/java-jwt/tree/3.0.0) (2016-12-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.0.0...3.0.1) @@ -41,4 +47,4 @@ The library implements JWT Verification and Signing using the following algorith | RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 | | ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 | | ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 | -| ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | \ No newline at end of file +| ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | diff --git a/README.md b/README.md index 42172e8d..af74bd18 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)]( com.auth0 java-jwt - 3.0.1 + 3.0.2 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.0.1' +compile 'com.auth0:java-jwt:3.0.2' ``` ## Available Algorithms From 5754aafc60d8ca3ba495a537cf1420458a3c2cea Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 13 Dec 2016 17:31:02 -0300 Subject: [PATCH 005/355] Add LICENSE Closes #122 --- LICENCE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENCE diff --git a/LICENCE b/LICENCE new file mode 100644 index 00000000..4a7a13ad --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Auth0, Inc. (http://auth0.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From ce9a6120e3a6dabb9fc014c799e52894920e6f4e Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 16:15:37 -0300 Subject: [PATCH 006/355] add getter for payload's Claims map --- README.md | 9 ++++++- .../main/java/com/auth0/jwt/JWTDecoder.java | 6 +++++ .../java/com/auth0/jwt/impl/PayloadImpl.java | 8 +++++++ .../com/auth0/jwt/interfaces/Payload.java | 10 +++++++- .../java/com/auth0/jwt/JWTDecoderTest.java | 17 +++++++++++++ .../com/auth0/jwt/impl/PayloadImplTest.java | 24 +++++++++++++++++++ 6 files changed, 72 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af74bd18..f81d4c1b 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,14 @@ String id = jwt.getId(); #### Private Claims -Additional Claims defined in the token's Payload can be obtained by calling `getClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. +Additional Claims defined in the token's Payload can be obtained by calling `getClaims()` or `getClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. + +```java +Map claims = jwt.getClaims(); //Key is the Claim name +Claim claim = claims.get("isAdmin"); +``` +al +or ```java Claim claim = jwt.getClaim("isAdmin"); diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 1c0c0afc..b7540287 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -11,6 +11,7 @@ import java.util.Date; import java.util.List; +import java.util.Map; /** * The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation. @@ -109,6 +110,11 @@ public Claim getClaim(String name) { return payload.getClaim(name); } + @Override + public Map getClaims() { + return payload.getClaims(); + } + @Override public String getSignature() { return signature; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index a4998942..dced3062 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -76,4 +76,12 @@ public Claim getClaim(String name) { return extractClaim(name, tree); } + @Override + public Map getClaims() { + Map claims = new HashMap<>(); + for (String name : tree.keySet()) { + claims.put(name, extractClaim(name, tree)); + } + return Collections.unmodifiableMap(claims); + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java index 2fd93cc2..0f639ab9 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java @@ -2,6 +2,7 @@ import java.util.Date; import java.util.List; +import java.util.Map; /** * The Payload class represents the 2nd part of the JWT, where the Payload value is hold. @@ -58,10 +59,17 @@ public interface Payload { String getId(); /** - * Get a Private Claim given it's name. If the Claim wasn't specified in the Payload, a NullClaim will be returned. + * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a NullClaim will be returned. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. */ Claim getClaim(String name); + + /** + * Get the Claims defined in the Token. + * + * @return a non-null Map containing the Claims defined in the Token. + */ + Map getClaims(); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index b3e74f6e..a4ab4376 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -13,6 +13,7 @@ import java.nio.charset.StandardCharsets; import java.util.Date; +import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -198,6 +199,22 @@ public void shouldGetNullClaimIfClaimValueIsNull() throws Exception { assertThat(jwt.getClaim("object").isNull(), is(true)); } + @Test + public void shouldGetAvailableClaims() throws Exception { + DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTIzNDU2Nzg5MCIsIm5iZiI6IjEyMzQ1Njc4OTAiLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.TX9Ct4feGp9YyeGK9Zl91tO0YBOrguJ4As9jeqgHdZQ"); + assertThat(jwt, is(notNullValue())); + assertThat(jwt.getClaims(), is(notNullValue())); + assertThat(jwt.getClaims(), is(instanceOf(Map.class))); + assertThat(jwt.getClaims().get("exp"), is(notNullValue())); + assertThat(jwt.getClaims().get("iat"), is(notNullValue())); + assertThat(jwt.getClaims().get("nbf"), is(notNullValue())); + assertThat(jwt.getClaims().get("jti"), is(notNullValue())); + assertThat(jwt.getClaims().get("aud"), is(notNullValue())); + assertThat(jwt.getClaims().get("sub"), is(notNullValue())); + assertThat(jwt.getClaims().get("iss"), is(notNullValue())); + assertThat(jwt.getClaims().get("extraClaim"), is(notNullValue())); + } + //Helper Methods private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) { diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index 6563f8e8..ca7562dc 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.TextNode; import org.hamcrest.collection.IsCollectionWithSize; @@ -153,4 +154,27 @@ public void shouldGetNotNullExtraClaimIfMissing() throws Exception { assertThat(payload.getClaim("missing"), is(notNullValue())); assertThat(payload.getClaim("missing"), is(instanceOf(NullClaim.class))); } + + @Test + public void shouldGetClaims() throws Exception { + Map tree = new HashMap<>(); + tree.put("extraClaim", new TextNode("extraValue")); + tree.put("sub", new TextNode("auth0")); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree); + assertThat(payload, is(notNullValue())); + Map claims = payload.getClaims(); + assertThat(claims, is(notNullValue())); + + assertThat(claims.get("extraClaim"), is(notNullValue())); + assertThat(claims.get("sub"), is(notNullValue())); + } + + @Test + public void shouldNotAllowToModifyClaimsMap() throws Exception { + assertThat(payload, is(notNullValue())); + Map claims = payload.getClaims(); + assertThat(claims, is(notNullValue())); + exception.expect(UnsupportedOperationException.class); + claims.put("name", null); + } } \ No newline at end of file From 1b9cc7a93c8118690131b3a0097d8184c9bc3655 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 11:10:36 -0300 Subject: [PATCH 007/355] add missing javadoc --- lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java b/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java index b672b474..56af6ff7 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java @@ -4,5 +4,10 @@ * Class that represents a Json Web Token that was decoded from it's string representation. */ public interface DecodedJWT extends Payload, Header, Signature { + /** + * Getter for the String Token used to create this JWT instance. + * + * @return the String Token. + */ String getToken(); } From e53d31d6df9c8a4c5828183168998e17078209c8 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 11:13:16 -0300 Subject: [PATCH 008/355] allow to add custom class claims --- .../main/java/com/auth0/jwt/JWTCreator.java | 11 +--- .../java/com/auth0/jwt/interfaces/Claim.java | 8 +-- .../java/com/auth0/jwt/JWTCreatorTest.java | 55 +++++++++++++++---- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 526a9000..a46f56b8 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -153,20 +153,15 @@ public Builder withJWTId(String jwtId) { /** * Add a custom Claim value. * - * @param name the Claim's name - * @param value the Claim's value. Must be an instance of Integer, Double, Boolean, Date or String class. + * @param name the Claim's name. + * @param value the Claim's value. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null or the value class is not allowed. + * @throws IllegalArgumentException if the name is null. */ public Builder withClaim(String name, Object value) throws IllegalArgumentException { - final boolean validValue = value instanceof Integer || value instanceof Double || - value instanceof Boolean || value instanceof Date || value instanceof String; if (name == null) { throw new IllegalArgumentException("The Custom Claim's name can't be null."); } - if (!validValue) { - throw new IllegalArgumentException("The Custom Claim's value class must be an instance of Integer, Double, Boolean, Date or String."); - } addClaim(name, value); return this; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 594ce254..6ba91fde 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -54,18 +54,18 @@ public interface Claim { /** * Get this Claim as an Array of type T. - * If the value isn't an Array, an empty Array will be returned. + * If the value isn't an Array, null will be returned. * - * @return the value as an Array or an empty Array. + * @return the value as an Array or null. * @throws JWTDecodeException if the values inside the Array can't be converted to a class T. */ T[] asArray(Class tClazz) throws JWTDecodeException; /** * Get this Claim as a List of type T. - * If the value isn't an Array, an empty List will be returned. + * If the value isn't an Array, null will be returned. * - * @return the value as a List or an empty List. + * @return the value as a List or null. * @throws JWTDecodeException if the values inside the List can't be converted to a class T. */ List asList(Class tClazz) throws JWTDecodeException; diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index fbb9d9a0..967dcb6a 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -5,9 +5,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -153,14 +151,6 @@ public void shouldThrowOnNullCustomClaimName() throws Exception { .withClaim(null, "value"); } - @Test - public void shouldThrowOnIllegalCustomClaimValueClass() throws Exception { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("The Custom Claim's value class must be an instance of Integer, Double, Boolean, Date or String."); - JWTCreator.init() - .withClaim("name", new Object()); - } - @Test public void shouldAcceptCustomClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() @@ -217,4 +207,47 @@ public void shouldAcceptCustomClaimOfTypeDate() throws Exception { assertThat(jwt, is(token)); } + @Test + public void shouldAcceptCustomClaimOfTypeArray() throws Exception { + String jwt = JWTCreator.init() + .withClaim("name", new Object[]{"text", 123, true}) + .sign(Algorithm.HMAC256("secret")); + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + + assertThat(jwt, is(notNullValue())); + assertThat(jwt, is(token)); + } + + @Test + public void shouldAcceptCustomClaimOfTypeList() throws Exception { + String jwt = JWTCreator.init() + .withClaim("name", Arrays.asList("text", 123, true)) + .sign(Algorithm.HMAC256("secret")); + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + + assertThat(jwt, is(notNullValue())); + assertThat(jwt, is(token)); + } + + @Test + public void shouldAcceptCustomClaimOfTypeMap() throws Exception { + String jwt = JWTCreator.init() + .withClaim("name", Collections.singletonMap("value", new Object[]{"text", 123, true})) + .sign(Algorithm.HMAC256("secret")); + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InZhbHVlIjpbInRleHQiLDEyMyx0cnVlXX19.CtZqZMoG__8yJQisT__pcv3NlynrkDl6qvq4sERx6D0"; + + assertThat(jwt, is(notNullValue())); + assertThat(jwt, is(token)); + } + + @Test + public void shouldAcceptCustomClaimOfTypeObject() throws Exception { + String jwt = JWTCreator.init() + .withClaim("name", new UserPojo("john", 123)) + .sign(Algorithm.HMAC256("secret")); + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7Im5hbWUiOiJqb2huIiwiaWQiOjEyM319.4ar5Q2vy8h7mw-FjFp1XRoiiKQrrPqdrSqEfATCGmNM"; + + assertThat(jwt, is(notNullValue())); + assertThat(jwt, is(token)); + } } \ No newline at end of file From 973f989541b7efcea047cee1ddbc9d823f1a013a Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 12:05:37 -0300 Subject: [PATCH 009/355] allow to get Claim as custom class --- README.md | 7 ++-- .../com/auth0/jwt/impl/JsonNodeClaim.java | 10 +++++ .../java/com/auth0/jwt/impl/NullClaim.java | 5 +++ .../java/com/auth0/jwt/interfaces/Claim.java | 8 ++++ ...aderImplTest.java => BasicHeaderTest.java} | 2 +- ...imImplTest.java => JsonNodeClaimTest.java} | 39 +++++++++++++++++-- 6 files changed, 62 insertions(+), 9 deletions(-) rename lib/src/test/java/com/auth0/jwt/impl/{HeaderImplTest.java => BasicHeaderTest.java} (99%) rename lib/src/test/java/com/auth0/jwt/impl/{ClaimImplTest.java => JsonNodeClaimTest.java} (86%) diff --git a/README.md b/README.md index f81d4c1b..cac3298e 100644 --- a/README.md +++ b/README.md @@ -286,8 +286,6 @@ JWT.require(Algorithm.HMAC256("secret")) .verify("my.jwt.token"); ``` -> The value of the custom Claim in all the cases must be of a `Integer`, `Double`, `Date`, `String`, or `Boolean` class. - ### Claim Class The Claim class is a wrapper for the Claim values. It allows you to get the Claim as different class types. The available helpers are: @@ -299,13 +297,14 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai * **asString()**: Returns the String value or null if it can't be converted. * **asDate()**: Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. -#### Collections +#### Custom Class and Collections To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. +* **as(class)**: Returns the value parsed as **Class Type**. * **asArray(class)**: Returns the value parsed as an Array of elements of type **Class Type**, or null if the value isn't a JSON Array. * **asList(class)**: Returns the value parsed as a List of elements of type **Class Type**, or null if the value isn't a JSON Array. -If the values inside the JSON Array can't be converted to the given **Class Type**, a `JWTDecodeException` will raise. +If the values can't be converted to the given **Class Type** a `JWTDecodeException` will raise. 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 287f1270..585f9ca3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -89,6 +89,16 @@ public List asList(Class tClazz) throws JWTDecodeException { return list; } + @Override + public T as(Class tClazz) throws JWTDecodeException { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.treeToValue(data, tClazz); + } catch (JsonProcessingException e) { + throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); + } + } + @Override public boolean isNull() { return !(data.isArray() || data.canConvertToLong() || data.isTextual() || data.isNumber() || data.isBoolean()); diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 3efb821e..755ef64d 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -49,4 +49,9 @@ public T[] asArray(Class tClazz) throws JWTDecodeException { public List asList(Class tClazz) throws JWTDecodeException { return null; } + + @Override + public T as(Class tClazz) throws JWTDecodeException { + return null; + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 6ba91fde..28c158f1 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -69,4 +69,12 @@ public interface Claim { * @throws JWTDecodeException if the values inside the List can't be converted to a class T. */ List asList(Class tClazz) throws JWTDecodeException; + + /** + * Get this Claim as a custom type T. + * + * @return the value as instance of T. + * @throws JWTDecodeException if the value can't be converted to a class T. + */ + T as(Class tClazz) throws JWTDecodeException; } diff --git a/lib/src/test/java/com/auth0/jwt/impl/HeaderImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java similarity index 99% rename from lib/src/test/java/com/auth0/jwt/impl/HeaderImplTest.java rename to lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java index 653921f6..d96baacf 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/HeaderImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java @@ -14,7 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class HeaderImplTest { +public class BasicHeaderTest { @Rule public ExpectedException exception = ExpectedException.none(); diff --git a/lib/src/test/java/com/auth0/jwt/impl/ClaimImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java similarity index 86% rename from lib/src/test/java/com/auth0/jwt/impl/ClaimImplTest.java rename to lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 5edaacb7..eb8a4ad7 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/ClaimImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,22 +7,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; +import org.hamcrest.collection.IsMapContaining; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.Arrays; -import java.util.Date; +import java.util.*; -import static com.auth0.jwt.impl.JsonNodeClaim.claimFromNode; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; +import static com.auth0.jwt.impl.JsonNodeClaim.claimFromNode; import static org.hamcrest.Matchers.*; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; -public class ClaimImplTest { +public class JsonNodeClaimTest { private ObjectMapper mapper; @Rule @@ -204,6 +204,37 @@ public void shouldThrowIfListClassMismatch() throws Exception { claim.asList(UserPojo.class); } + @Test + public void shouldGetCustomClassValue() throws Exception { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim.as(UserPojo.class).getName(), is("john")); + assertThat(claim.as(UserPojo.class).getId(), is(123)); + } + + @Test + public void shouldThrowIfCustomClassMismatch() throws Exception { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + Claim claim = claimFromNode(value); + + exception.expect(JWTDecodeException.class); + claim.as(String.class); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldGetAsMapValue() throws Exception { + JsonNode value = mapper.valueToTree(Collections.singletonMap("key", new UserPojo("john", 123))); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + Map map = claim.as(Map.class); + assertThat(((HashMap) map.get("key")), IsMapContaining.hasEntry("name", "john")); + assertThat(((HashMap) map.get("key")), IsMapContaining.hasEntry("id", 123)); + } + @Test public void shouldReturnBaseClaimWhenParsingMissingNode() throws Exception { JsonNode value = MissingNode.getInstance(); From 59ac5e4423bf1b93da210782aca6e51dfa71453b Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 14:16:57 -0300 Subject: [PATCH 010/355] allow to require a custom class Claim --- README.md | 2 +- .../main/java/com/auth0/jwt/JWTVerifier.java | 13 +++--- .../com/auth0/jwt/impl/JsonNodeClaim.java | 5 ++- .../java/com/auth0/jwt/JWTVerifierTest.java | 44 ++++++++++++++----- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 12 ++--- ...{BaseClaimTest.java => NullClaimTest.java} | 7 ++- 6 files changed, 55 insertions(+), 28 deletions(-) rename lib/src/test/java/com/auth0/jwt/impl/{BaseClaimTest.java => NullClaimTest.java} (89%) diff --git a/README.md b/README.md index cac3298e..5b66383d 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai #### Custom Class and Collections To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. -* **as(class)**: Returns the value parsed as **Class Type**. +* **as(class)**: Returns the value parsed as **Class Type**. For collections you should use the `asArray` and `asList` methods. * **asArray(class)**: Returns the value parsed as an Array of elements of type **Class Type**, or null if the value isn't a JSON Array. * **asList(class)**: Returns the value parsed as a List of elements of type **Class Type**, or null if the value isn't a JSON Array. diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index b9077cb2..cb9e0e1a 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -168,20 +168,15 @@ public Verification withJWTId(String jwtId) { /** * Require a specific Claim value. * - * @param name the Claim's name - * @param value the Claim's value. Must be an instance of Integer, Double, Boolean, Date or String class. + * @param name the Claim's name. + * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null or the value class is not allowed. + * @throws IllegalArgumentException if the name is null. */ public Verification withClaim(String name, Object value) throws IllegalArgumentException { - final boolean validValue = value instanceof Integer || value instanceof Double || - value instanceof Boolean || value instanceof Date || value instanceof String; if (name == null) { throw new IllegalArgumentException("The Custom Claim's name can't be null."); } - if (!validValue) { - throw new IllegalArgumentException("The Custom Claim's value class must be an instance of Integer, Double, Boolean, Date or String."); - } requireClaim(name, value); return this; @@ -301,6 +296,8 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { isValid = value.equals(claim.asDouble()); } else if (value instanceof Date) { isValid = value.equals(claim.asDate()); + } else { + isValid = Objects.deepEquals(value, claim.as(value.getClass())); } if (!isValid) { 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 585f9ca3..317b4946 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Date; @@ -93,8 +94,8 @@ public List asList(Class tClazz) throws JWTDecodeException { public T as(Class tClazz) throws JWTDecodeException { ObjectMapper mapper = new ObjectMapper(); try { - return mapper.treeToValue(data, tClazz); - } catch (JsonProcessingException e) { + return mapper.treeAsTokens(data).readValueAs(tClazz); + } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index fe7b1cf5..0cfe1f19 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -8,9 +8,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; @@ -132,14 +130,6 @@ public void shouldThrowOnNullCustomClaimName() throws Exception { .withClaim(null, "value"); } - @Test - public void shouldThrowOnIllegalCustomClaimValueClass() throws Exception { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("The Custom Claim's value class must be an instance of Integer, Double, Boolean, Date or String."); - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", new Object()); - } - @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeString() throws Exception { exception.expect(InvalidClaimException.class); @@ -263,6 +253,38 @@ public void shouldValidateCustomClaimOfTypeDate() throws Exception { assertThat(jwt, is(notNullValue())); } + @Test + public void shouldValidateCustomClaimOfCustomType() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7Im5hbWUiOiJqb2huIiwiaWQiOjEyM319.j3e7IfnEchQEwgDs1icOyufhzAyNOYfX9fjJwV6uyZk"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("user", new UserPojo("john", 123)) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldValidateCustomClaimOfTypeArray() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", new Object[]{"text", 123, true}) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldValidateCustomClaimOfTypeList() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", new ArrayList<>(Arrays.asList("text", 123, true))) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } // Generic Delta @SuppressWarnings("RedundantCast") 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 eb8a4ad7..7a5dbc35 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,13 +7,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; -import org.hamcrest.collection.IsMapContaining; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Map; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static com.auth0.jwt.impl.JsonNodeClaim.claimFromNode; @@ -223,7 +225,7 @@ public void shouldThrowIfCustomClassMismatch() throws Exception { claim.as(String.class); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "RedundantCast"}) @Test public void shouldGetAsMapValue() throws Exception { JsonNode value = mapper.valueToTree(Collections.singletonMap("key", new UserPojo("john", 123))); @@ -231,8 +233,8 @@ public void shouldGetAsMapValue() throws Exception { assertThat(claim, is(notNullValue())); Map map = claim.as(Map.class); - assertThat(((HashMap) map.get("key")), IsMapContaining.hasEntry("name", "john")); - assertThat(((HashMap) map.get("key")), IsMapContaining.hasEntry("id", 123)); + assertThat(((Map) map.get("key")), hasEntry("name", (Object) "john")); + assertThat(((Map) map.get("key")), hasEntry("id", (Object) 123)); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/impl/BaseClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java similarity index 89% rename from lib/src/test/java/com/auth0/jwt/impl/BaseClaimTest.java rename to lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index bd4b5dec..9796c322 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/BaseClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; -public class BaseClaimTest { +public class NullClaimTest { private NullClaim claim; @Before @@ -56,4 +56,9 @@ public void shouldGetAsList() throws Exception { assertThat(claim.asList(Object.class), is(nullValue())); } + @Test + public void shouldGetAsCustomClass() throws Exception { + assertThat(claim.as(Object.class), is(nullValue())); + } + } \ No newline at end of file From 07ea314c4d67273a046df26a1f48bbdd38e1ad71 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 16 Dec 2016 19:04:11 -0300 Subject: [PATCH 011/355] restrict again the classes to verify but allow arrays --- README.md | 2 + .../main/java/com/auth0/jwt/JWTVerifier.java | 123 +++++++++++++++--- .../java/com/auth0/jwt/JWTVerifierTest.java | 27 ++-- 3 files changed, 116 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 5b66383d..8399a625 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,8 @@ JWT.require(Algorithm.HMAC256("secret")) .verify("my.jwt.token"); ``` +> Currently supported classes for custom Claim verification are: Boolean, Integer, Double, String, Date and Array of types String and Integer. + ### Claim Class The Claim class is a wrapper for the Claim values. It allows you to get the Claim as different class types. The available helpers are: diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index cb9e0e1a..3f554985 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -99,9 +99,7 @@ public Verification withAudience(String... audience) { * @throws IllegalArgumentException if leeway is negative. */ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } + assertPositive(leeway); this.defaultLeeway = leeway; return this; } @@ -115,9 +113,7 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { * @throws IllegalArgumentException if leeway is negative. */ public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } + assertPositive(leeway); requireClaim(PublicClaims.EXPIRES_AT, leeway); return this; } @@ -131,9 +127,7 @@ public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException * @throws IllegalArgumentException if leeway is negative. */ public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } + assertPositive(leeway); requireClaim(PublicClaims.NOT_BEFORE, leeway); return this; } @@ -147,9 +141,7 @@ public Verification acceptNotBefore(long leeway) throws IllegalArgumentException * @throws IllegalArgumentException if leeway is negative. */ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } + assertPositive(leeway); requireClaim(PublicClaims.ISSUED_AT, leeway); return this; } @@ -173,15 +165,96 @@ public Verification withJWTId(String jwtId) { * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ - public Verification withClaim(String name, Object value) throws IllegalArgumentException { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } + public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); requireClaim(name, value); return this; } + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + /** * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. * @@ -203,6 +276,18 @@ JWTVerifier build(Clock clock) { return new JWTVerifier(algorithm, claims, clock); } + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + private void addLeewayToDateClaims() { if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); @@ -296,8 +381,10 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { isValid = value.equals(claim.asDouble()); } else if (value instanceof Date) { isValid = value.equals(claim.asDate()); - } else { - isValid = Objects.deepEquals(value, claim.as(value.getClass())); + } else if (value instanceof Object[]) { + List claimArr = Arrays.asList(claim.as(Object[].class)); + List valueArr = Arrays.asList((Object[]) value); + isValid = claimArr.containsAll(valueArr); } if (!isValid) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 0cfe1f19..73b57046 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -8,7 +8,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; @@ -254,10 +256,10 @@ public void shouldValidateCustomClaimOfTypeDate() throws Exception { } @Test - public void shouldValidateCustomClaimOfCustomType() throws Exception { - String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7Im5hbWUiOiJqb2huIiwiaWQiOjEyM319.j3e7IfnEchQEwgDs1icOyufhzAyNOYfX9fjJwV6uyZk"; + public void shouldValidateCustomArrayClaimOfTypeString() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.lxM8EcmK1uSZRAPd0HUhXGZJdauRmZmLjoeqz4J9yAA"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("user", new UserPojo("john", 123)) + .withArrayClaim("name", "text", "123", "true") .build() .verify(token); @@ -265,21 +267,10 @@ public void shouldValidateCustomClaimOfCustomType() throws Exception { } @Test - public void shouldValidateCustomClaimOfTypeArray() throws Exception { - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + public void shouldValidateCustomArrayClaimOfTypeInteger() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", new Object[]{"text", 123, true}) - .build() - .verify(token); - - assertThat(jwt, is(notNullValue())); - } - - @Test - public void shouldValidateCustomClaimOfTypeList() throws Exception { - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", new ArrayList<>(Arrays.asList("text", 123, true))) + .withArrayClaim("name", 1, 2, 3) .build() .verify(token); From 3725f4f1cb75be1ff255f324367a8e9ea20e8900 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 16 Dec 2016 19:38:31 -0300 Subject: [PATCH 012/355] restrict again the classes for create. allow arrays --- README.md | 6 +- .../main/java/com/auth0/jwt/JWTCreator.java | 95 ++++++++++++++++++- .../java/com/auth0/jwt/JWTCreatorTest.java | 38 ++------ 3 files changed, 104 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 8399a625..01ac6eba 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,7 @@ When creating a Token with the `JWT.create()` you can specify a custom Claim by ```java JWT.create() .withClaim("name", 123) + .withArrayClaim("array", new Integer[]{1, 2, 3}) .sign(Algorithm.HMAC256("secret")); ``` @@ -282,11 +283,12 @@ You can also verify custom Claims on the `JWT.require()` by calling `withClaim() ```java JWT.require(Algorithm.HMAC256("secret")) .withClaim("name", 123) + .withArrayClaim("array", 1, 2, 3) .build() .verify("my.jwt.token"); ``` -> Currently supported classes for custom Claim verification are: Boolean, Integer, Double, String, Date and Array of types String and Integer. +> Currently supported classes for custom JWT Claim creation and verification are: Boolean, Integer, Double, String, Date and Arrays of type String and Integer. ### Claim Class @@ -299,7 +301,7 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai * **asString()**: Returns the String value or null if it can't be converted. * **asDate()**: Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. -#### Custom Class and Collections +#### Custom Classes and Collections To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. * **as(class)**: Returns the value parsed as **Class Type**. For collections you should use the `asArray` and `asList` methods. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a46f56b8..4ab89f34 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -158,15 +158,96 @@ public Builder withJWTId(String jwtId) { * @return this same Builder instance. * @throws IllegalArgumentException if the name is null. */ - public Builder withClaim(String name, Object value) throws IllegalArgumentException { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } + public Builder withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, value); + return this; + } + /** + * Add a custom Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); addClaim(name, value); return this; } + /** + * Add a custom Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, value); + return this; + } + + /** + * Add a custom Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, value); + return this; + } + + /** + * Add a custom Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, value); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, String[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + /** * Creates a new JWT and signs is with the given algorithm * @@ -183,6 +264,12 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea return new JWTCreator(algorithm, headerClaims, payloadClaims).sign(); } + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + private void addClaim(String name, Object value) { if (value == null) { payloadClaims.remove(name); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 967dcb6a..2bb47989 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -5,7 +5,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -208,44 +210,22 @@ public void shouldAcceptCustomClaimOfTypeDate() throws Exception { } @Test - public void shouldAcceptCustomClaimOfTypeArray() throws Exception { + public void shouldAcceptCustomArrayClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() - .withClaim("name", new Object[]{"text", 123, true}) + .withArrayClaim("name", new String[]{"text", "123", "true"}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.lxM8EcmK1uSZRAPd0HUhXGZJdauRmZmLjoeqz4J9yAA"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); } @Test - public void shouldAcceptCustomClaimOfTypeList() throws Exception { + public void shouldAcceptCustomArrayClaimOfTypeInteger() throws Exception { String jwt = JWTCreator.init() - .withClaim("name", Arrays.asList("text", 123, true)) + .withArrayClaim("name", new Integer[]{1, 2, 3}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLDEyMyx0cnVlXX0.uSulPFzLSbgfG8Lpr0jq0JDMhDlGGeQrx09PHEymu1E"; - - assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); - } - - @Test - public void shouldAcceptCustomClaimOfTypeMap() throws Exception { - String jwt = JWTCreator.init() - .withClaim("name", Collections.singletonMap("value", new Object[]{"text", 123, true})) - .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InZhbHVlIjpbInRleHQiLDEyMyx0cnVlXX19.CtZqZMoG__8yJQisT__pcv3NlynrkDl6qvq4sERx6D0"; - - assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); - } - - @Test - public void shouldAcceptCustomClaimOfTypeObject() throws Exception { - String jwt = JWTCreator.init() - .withClaim("name", new UserPojo("john", 123)) - .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7Im5hbWUiOiJqb2huIiwiaWQiOjEyM319.4ar5Q2vy8h7mw-FjFp1XRoiiKQrrPqdrSqEfATCGmNM"; + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); From 24901e20461f2d03bb0052a21645143c129631b6 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 4 Jan 2017 17:36:24 -0300 Subject: [PATCH 013/355] pass charset to String.getBytes() on tests (#128) --- .../auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 2 +- .../auth0/jwt/algorithms/HMACAlgorithmTest.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) 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 c2b8f574..e1c6bd61 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -427,7 +427,7 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); - algorithm.sign(ES256Header.getBytes()); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 1c8a2012..7e25c66f 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -38,7 +38,7 @@ public void shouldGetStringBytes() throws Exception { public void shouldPassHMAC256Verification() throws Exception { String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; Algorithm algorithmString = Algorithm.HMAC256("secret"); - Algorithm algorithmBytes = Algorithm.HMAC256("secret".getBytes()); + Algorithm algorithmBytes = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithmString, jwt); AlgorithmUtils.verify(algorithmBytes, jwt); } @@ -57,7 +57,7 @@ public void shouldFailHMAC256VerificationWithInvalidSecretBytes() throws Excepti exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256"); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; - Algorithm algorithm = Algorithm.HMAC256("not_real_secret".getBytes()); + Algorithm algorithm = Algorithm.HMAC256("not_real_secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithm, jwt); } @@ -65,7 +65,7 @@ public void shouldFailHMAC256VerificationWithInvalidSecretBytes() throws Excepti public void shouldPassHMAC384Verification() throws Exception { String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; Algorithm algorithmString = Algorithm.HMAC384("secret"); - Algorithm algorithmBytes = Algorithm.HMAC384("secret".getBytes()); + Algorithm algorithmBytes = Algorithm.HMAC384("secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithmString, jwt); AlgorithmUtils.verify(algorithmBytes, jwt); } @@ -84,7 +84,7 @@ public void shouldFailHMAC384VerificationWithInvalidSecretBytes() throws Excepti exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA384"); String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; - Algorithm algorithm = Algorithm.HMAC384("not_real_secret".getBytes()); + Algorithm algorithm = Algorithm.HMAC384("not_real_secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithm, jwt); } @@ -92,7 +92,7 @@ public void shouldFailHMAC384VerificationWithInvalidSecretBytes() throws Excepti public void shouldPassHMAC512Verification() throws Exception { String jwt = "eyJhbGciOiJIUzUxMiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.VUo2Z9SWDV-XcOc_Hr6Lff3vl7L9e5Vb8ThXpmGDFjHxe3Dr1ZBmUChYF-xVA7cAdX1P_D4ZCUcsv3IefpVaJw"; Algorithm algorithmString = Algorithm.HMAC512("secret"); - Algorithm algorithmBytes = Algorithm.HMAC512("secret".getBytes()); + Algorithm algorithmBytes = Algorithm.HMAC512("secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithmString, jwt); AlgorithmUtils.verify(algorithmBytes, jwt); } @@ -111,7 +111,7 @@ public void shouldFailHMAC512VerificationWithInvalidSecretBytes() throws Excepti exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512"); String jwt = "eyJhbGciOiJIUzUxMiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.VUo2Z9SWDV-XcOc_Hr6Lff3vl7L9e5Vb8ThXpmGDFjHxe3Dr1ZBmUChYF-xVA7cAdX1P_D4ZCUcsv3IefpVaJw"; - Algorithm algorithm = Algorithm.HMAC512("not_real_secret".getBytes()); + Algorithm algorithm = Algorithm.HMAC512("not_real_secret".getBytes(StandardCharsets.UTF_8)); AlgorithmUtils.verify(algorithm, jwt); } @@ -125,7 +125,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); - Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes()); + Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; AlgorithmUtils.verify(algorithm, jwt); } @@ -140,7 +140,7 @@ public void shouldThrowOnVerifyWhenTheSecretIsInvalid() throws Exception { when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) .thenThrow(InvalidKeyException.class); - Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes()); + Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; AlgorithmUtils.verify(algorithm, jwt); } From 0abcebe5d6d478ad8b25b089da1a03a38471a1ba Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 17:29:48 -0300 Subject: [PATCH 014/355] allow to change the Clock used by the JWTVerifier --- README.md | 9 +++++ lib/src/main/java/com/auth0/jwt/Clock.java | 6 +-- lib/src/main/java/com/auth0/jwt/JWT.java | 9 ++--- .../main/java/com/auth0/jwt/JWTVerifier.java | 27 ++++++++++--- .../auth0/jwt/interfaces/Verification.java | 39 +++++++++++++++++++ lib/src/test/java/com/auth0/jwt/JWTTest.java | 12 ++++-- .../java/com/auth0/jwt/JWTVerifierTest.java | 33 ++++++++++------ 7 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/Verification.java diff --git a/README.md b/README.md index 01ac6eba..b9d3458b 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,15 @@ JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) .build(); ``` +If you need to test this behaviour in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a custom `Clock`. e.g.: + +```java +BaseVerification verification = (BaseVerification) JWT.require(Algorithm.RSA256(key)) + .acceptLeeway(1) + .acceptExpiresAt(5); +Clock clock = new Clock(); +JWTVerifier verifier = verification.build(clock); +``` ### Header Claims diff --git a/lib/src/main/java/com/auth0/jwt/Clock.java b/lib/src/main/java/com/auth0/jwt/Clock.java index b2f526cc..ae9f0909 100644 --- a/lib/src/main/java/com/auth0/jwt/Clock.java +++ b/lib/src/main/java/com/auth0/jwt/Clock.java @@ -5,9 +5,9 @@ /** * The Clock class is used to wrap calls to Date class. */ -class Clock { +public class Clock { - Clock() { + public Clock() { } /** @@ -15,7 +15,7 @@ class Clock { * * @return a new Date representing Today's time. */ - Date getToday() { + public Date getToday() { return new Date(); } } diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index 88940fd3..c8f09c4d 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -2,18 +2,15 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; - -import java.util.Date; -import java.util.List; +import com.auth0.jwt.interfaces.Verification; @SuppressWarnings("WeakerAccess") public abstract class JWT implements DecodedJWT { /** * Decode a given JWT token. - * + *

* Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. @@ -31,7 +28,7 @@ public static JWT decode(String token) throws JWTDecodeException { * @return {@link JWTVerifier} builder * @throws IllegalArgumentException if the provided algorithm is null. */ - public static JWTVerifier.Verification require(Algorithm algorithm) { + public static Verification require(Algorithm algorithm) { return JWTVerifier.init(algorithm); } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 3f554985..3faf4f21 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -8,6 +8,7 @@ import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.Verification; import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; @@ -35,19 +36,19 @@ public final class JWTVerifier { * @return a JWTVerifier.Verification instance to configure. * @throws IllegalArgumentException if the provided algorithm is null. */ - static JWTVerifier.Verification init(Algorithm algorithm) throws IllegalArgumentException { - return new Verification(algorithm); + static Verification init(Algorithm algorithm) throws IllegalArgumentException { + return new BaseVerification(algorithm); } /** * The Verification class holds the Claims required by a JWT to be valid. */ - public static class Verification { + public static class BaseVerification implements Verification { private final Algorithm algorithm; private final Map claims; private long defaultLeeway; - Verification(Algorithm algorithm) throws IllegalArgumentException { + BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { throw new IllegalArgumentException("The Algorithm cannot be null."); } @@ -63,6 +64,7 @@ public static class Verification { * @param issuer the required Issuer value * @return this same Verification instance. */ + @Override public Verification withIssuer(String issuer) { requireClaim(PublicClaims.ISSUER, issuer); return this; @@ -74,6 +76,7 @@ public Verification withIssuer(String issuer) { * @param subject the required Subject value * @return this same Verification instance. */ + @Override public Verification withSubject(String subject) { requireClaim(PublicClaims.SUBJECT, subject); return this; @@ -85,6 +88,7 @@ public Verification withSubject(String subject) { * @param audience the required Audience value * @return this same Verification instance. */ + @Override public Verification withAudience(String... audience) { requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); return this; @@ -98,6 +102,7 @@ public Verification withAudience(String... audience) { * @return this same Verification instance. * @throws IllegalArgumentException if leeway is negative. */ + @Override public Verification acceptLeeway(long leeway) throws IllegalArgumentException { assertPositive(leeway); this.defaultLeeway = leeway; @@ -112,6 +117,7 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { * @return this same Verification instance. * @throws IllegalArgumentException if leeway is negative. */ + @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); requireClaim(PublicClaims.EXPIRES_AT, leeway); @@ -126,6 +132,7 @@ public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException * @return this same Verification instance. * @throws IllegalArgumentException if leeway is negative. */ + @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); requireClaim(PublicClaims.NOT_BEFORE, leeway); @@ -140,6 +147,7 @@ public Verification acceptNotBefore(long leeway) throws IllegalArgumentException * @return this same Verification instance. * @throws IllegalArgumentException if leeway is negative. */ + @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); requireClaim(PublicClaims.ISSUED_AT, leeway); @@ -152,6 +160,7 @@ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException * @param jwtId the required Id value * @return this same Verification instance. */ + @Override public Verification withJWTId(String jwtId) { requireClaim(PublicClaims.JWT_ID, jwtId); return this; @@ -165,6 +174,7 @@ public Verification withJWTId(String jwtId) { * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, value); @@ -179,6 +189,7 @@ public Verification withClaim(String name, Boolean value) throws IllegalArgument * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, value); @@ -193,6 +204,7 @@ public Verification withClaim(String name, Integer value) throws IllegalArgument * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, value); @@ -207,6 +219,7 @@ public Verification withClaim(String name, Double value) throws IllegalArgumentE * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, value); @@ -221,6 +234,7 @@ public Verification withClaim(String name, String value) throws IllegalArgumentE * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withClaim(String name, Date value) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, value); @@ -235,6 +249,7 @@ public Verification withClaim(String name, Date value) throws IllegalArgumentExc * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, items); @@ -249,6 +264,7 @@ public Verification withArrayClaim(String name, String... items) throws IllegalA * @return this same Verification instance. * @throws IllegalArgumentException if the name is null. */ + @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, items); @@ -260,6 +276,7 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal * * @return a new JWTVerifier instance. */ + @Override public JWTVerifier build() { return this.build(new Clock()); } @@ -271,7 +288,7 @@ public JWTVerifier build() { * @param clock the instance that will handle the current time. * @return a new JWTVerifier instance with a custom Clock. */ - JWTVerifier build(Clock clock) { + public JWTVerifier build(Clock clock) { addLeewayToDateClaims(); return new JWTVerifier(algorithm, claims, clock); } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java new file mode 100644 index 00000000..3d2ac60f --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -0,0 +1,39 @@ +package com.auth0.jwt.interfaces; + +import com.auth0.jwt.JWTVerifier; + +import java.util.Date; + +public interface Verification { + Verification withIssuer(String issuer); + + Verification withSubject(String subject); + + Verification withAudience(String... audience); + + Verification acceptLeeway(long leeway) throws IllegalArgumentException; + + Verification acceptExpiresAt(long leeway) throws IllegalArgumentException; + + Verification acceptNotBefore(long leeway) throws IllegalArgumentException; + + Verification acceptIssuedAt(long leeway) throws IllegalArgumentException; + + Verification withJWTId(String jwtId); + + Verification withClaim(String name, Boolean value) throws IllegalArgumentException; + + Verification withClaim(String name, Integer value) throws IllegalArgumentException; + + Verification withClaim(String name, Double value) throws IllegalArgumentException; + + Verification withClaim(String name, String value) throws IllegalArgumentException; + + Verification withClaim(String name, Date value) throws IllegalArgumentException; + + Verification withArrayClaim(String name, String... items) throws IllegalArgumentException; + + Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; + + JWTVerifier build(); +} diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 18e9cd9c..a3fd2f54 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -242,7 +242,8 @@ public void shouldGetExpirationTime() throws Exception { when(clock.getToday()).thenReturn(expectedDate); String token = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc1OTJ9.x_ZjkPkKYUV5tdvc0l8go6D_z2kez1MQcOxokXrDc3k"; - DecodedJWT jwt = JWT.require(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -259,7 +260,8 @@ public void shouldGetNotBefore() throws Exception { when(clock.getToday()).thenReturn(expectedDate); String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTJ9.mWYSOPoNXstjKbZkKrqgkwPOQWEx3F3gMm6PMcfuJd8"; - DecodedJWT jwt = JWT.require(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -276,7 +278,8 @@ public void shouldGetIssuedAt() throws Exception { when(clock.getToday()).thenReturn(expectedDate); String token = "eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.5o1CKlLFjKKcddZzoarQ37pq7qZqNPav3sdZ_bsZaD4"; - DecodedJWT jwt = JWT.require(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -289,7 +292,8 @@ public void shouldGetIssuedAt() throws Exception { @Test public void shouldGetId() throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkwIn0.m3zgEfVUFOd-CvL3xG5BuOWLzb0zMQZCqiVNQQOPOvA"; - DecodedJWT jwt = JWT.require(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build() .verify(token); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 73b57046..7cce2ff1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -366,8 +366,9 @@ public void shouldValidateExpiresAtWithLeeway() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE + 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .acceptExpiresAt(2) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) + .acceptExpiresAt(2); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -380,7 +381,8 @@ public void shouldValidateExpiresAtIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -395,7 +397,8 @@ public void shouldThrowOnInvalidExpiresAtIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE + 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification .build(clock) .verify(token); } @@ -416,8 +419,9 @@ public void shouldValidateNotBeforeWithLeeway() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .acceptNotBefore(2) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) + .acceptNotBefore(2); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -432,7 +436,8 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; - JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification .build(clock) .verify(token); } @@ -443,7 +448,8 @@ public void shouldValidateNotBeforeIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -466,8 +472,9 @@ public void shouldValidateIssuedAtWithLeeway() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .acceptIssuedAt(2) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) + .acceptIssuedAt(2); + DecodedJWT jwt = verification .build(clock) .verify(token); @@ -482,7 +489,8 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification .build(clock) .verify(token); } @@ -493,7 +501,8 @@ public void shouldValidateIssuedAtIfPresent() throws Exception { when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification .build(clock) .verify(token); From 545f5c41e1deb78079d25ce35ac768d9995c2a0c Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 14 Dec 2016 17:45:24 -0300 Subject: [PATCH 015/355] extract Clock interface. Make ClockImpl package private --- README.md | 2 +- lib/src/main/java/com/auth0/jwt/ClockImpl.java | 16 ++++++++++++++++ lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 3 ++- .../com/auth0/jwt/{ => interfaces}/Clock.java | 11 +++-------- .../jwt/{ClockTest.java => ClockImplTest.java} | 6 +++--- lib/src/test/java/com/auth0/jwt/JWTTest.java | 1 + .../test/java/com/auth0/jwt/JWTVerifierTest.java | 3 ++- 7 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/ClockImpl.java rename lib/src/main/java/com/auth0/jwt/{ => interfaces}/Clock.java (62%) rename lib/src/test/java/com/auth0/jwt/{ClockTest.java => ClockImplTest.java} (77%) diff --git a/README.md b/README.md index b9d3458b..56ceecb1 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ If you need to test this behaviour in your lib/app cast the `Verification` insta BaseVerification verification = (BaseVerification) JWT.require(Algorithm.RSA256(key)) .acceptLeeway(1) .acceptExpiresAt(5); -Clock clock = new Clock(); +Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock); ``` diff --git a/lib/src/main/java/com/auth0/jwt/ClockImpl.java b/lib/src/main/java/com/auth0/jwt/ClockImpl.java new file mode 100644 index 00000000..45e3edfc --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/ClockImpl.java @@ -0,0 +1,16 @@ +package com.auth0.jwt; + +import com.auth0.jwt.interfaces.Clock; + +import java.util.Date; + +final class ClockImpl implements Clock { + + ClockImpl() { + } + + @Override + public Date getToday() { + return new Date(); + } +} diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 3faf4f21..fde08b8a 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -7,6 +7,7 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import org.apache.commons.codec.binary.Base64; @@ -278,7 +279,7 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal */ @Override public JWTVerifier build() { - return this.build(new Clock()); + return this.build(new ClockImpl()); } /** diff --git a/lib/src/main/java/com/auth0/jwt/Clock.java b/lib/src/main/java/com/auth0/jwt/interfaces/Clock.java similarity index 62% rename from lib/src/main/java/com/auth0/jwt/Clock.java rename to lib/src/main/java/com/auth0/jwt/interfaces/Clock.java index ae9f0909..7dd9c43b 100644 --- a/lib/src/main/java/com/auth0/jwt/Clock.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Clock.java @@ -1,21 +1,16 @@ -package com.auth0.jwt; +package com.auth0.jwt.interfaces; import java.util.Date; /** * The Clock class is used to wrap calls to Date class. */ -public class Clock { - - public Clock() { - } +public interface Clock { /** * Returns a new Date representing Today's time. * * @return a new Date representing Today's time. */ - public Date getToday() { - return new Date(); - } + Date getToday(); } diff --git a/lib/src/test/java/com/auth0/jwt/ClockTest.java b/lib/src/test/java/com/auth0/jwt/ClockImplTest.java similarity index 77% rename from lib/src/test/java/com/auth0/jwt/ClockTest.java rename to lib/src/test/java/com/auth0/jwt/ClockImplTest.java index 23cf8df3..0eec07d7 100644 --- a/lib/src/test/java/com/auth0/jwt/ClockTest.java +++ b/lib/src/test/java/com/auth0/jwt/ClockImplTest.java @@ -1,19 +1,19 @@ package com.auth0.jwt; +import com.auth0.jwt.interfaces.Clock; import org.junit.Test; import java.util.Date; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.*; -public class ClockTest { +public class ClockImplTest { @Test public void shouldGetToday() throws Exception{ - Clock clock = new Clock(); + Clock clock = new ClockImpl(); Date clockToday = clock.getToday(); assertThat(clockToday, is(notNullValue())); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index a3fd2f54..d54af0d7 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -1,6 +1,7 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 7cce2ff1..b7db94da 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; +import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.Rule; import org.junit.Test; @@ -195,7 +196,7 @@ public void shouldThrowOnInvalidCustomClaimValue() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; Map map = new HashMap<>(); map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new Clock()); + JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl()); verifier.verify(token); } From 140802c0d74fe39a8c85aff1918ecaf1ba0f8015 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Wed, 4 Jan 2017 19:39:44 -0300 Subject: [PATCH 016/355] Release 3.1.0 --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69bd568c..6eb9c107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [3.1.0](https://github.com/auth0/java-jwt/tree/3.1.0) (2017-01-04) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.0.2...3.1.0) + +**Added** +- Make Clock customization accessible for verification [\#125](https://github.com/auth0/java-jwt/pull/125) ([lbalmaceda](https://github.com/lbalmaceda)) +- Add getter for all the Payload's Claims [\#124](https://github.com/auth0/java-jwt/pull/124) ([lbalmaceda](https://github.com/lbalmaceda)) +- Accept Array type on verification and creation. [\#123](https://github.com/auth0/java-jwt/pull/123) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.0.2](https://github.com/auth0/java-jwt/tree/3.0.2) (2016-12-13) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.0.1...3.0.2) diff --git a/README.md b/README.md index 56ceecb1..8fe15e62 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)]( com.auth0 java-jwt - 3.0.2 + 3.1.0 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.0.2' +compile 'com.auth0:java-jwt:3.1.0' ``` ## Available Algorithms From 1a959950d56758b9f001e076020626a0357b0f24 Mon Sep 17 00:00:00 2001 From: Rafal Date: Fri, 6 Jan 2017 13:53:08 +0000 Subject: [PATCH 017/355] Fix javadoc comment (#130) --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index fde08b8a..0ee93f76 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -96,7 +96,7 @@ public Verification withAudience(String... audience) { } /** - * Define the default window in milliseconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. * Setting a specific leeway value on a given Claim will override this value for that Claim. * * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. From 4313d5e331e58ceede16d4605dba55ba8a3ece5a Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 17 Jan 2017 14:43:22 +0100 Subject: [PATCH 018/355] Fixed link to Auth0 Homepage (#133) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fe15e62..d4726a4c 100644 --- a/README.md +++ b/README.md @@ -343,7 +343,7 @@ If you have found a bug or if you have a feature request, please report them at ## Author -[Auth0](auth0.com) +[Auth0](https://auth0.com/) ## License From f8de6f0bea6aaa3d4d1563f2793e19b7c814169c Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 13:19:37 -0300 Subject: [PATCH 019/355] mention JWTDecode.Android in the readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4726a4c..37e151bf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt/v3.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) -A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) +A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). + +If you're looking for an *Android* version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. ## Installation From b29626229afc987f1e8d6dab7f3886b967d4c85a Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 16:07:26 -0300 Subject: [PATCH 020/355] fix long to date conversion --- lib/build.gradle | 2 +- .../com/auth0/jwt/impl/PayloadSerializer.java | 4 ++-- .../jwt/impl/PayloadDeserializerTest.java | 13 +++++++++++ .../auth0/jwt/impl/PayloadSerializerTest.java | 23 +++++++++++++++++-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 4d2a5c38..75b93a92 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -52,7 +52,7 @@ jacocoTestReport { test { testLogging { - events "passed", "skipped", "failed", "standardError" + events "skipped", "failed", "standardError" exceptionFormat "short" } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 2fc9c617..29f287eb 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -54,7 +54,7 @@ public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider gen.writeObject(safePayload); } - private int dateToSeconds(Date date) { - return (int) (date.getTime() / 1000); + private long dateToSeconds(Date date) { + return date.getTime() / 1000; } } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index faf9dd4e..014ae838 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -219,6 +219,19 @@ public void shouldGetDateWhenParsingNumericNode() throws Exception { assertThat(date.getTime(), is(seconds * 1000)); } + @Test + public void shouldGetLargeDateWhenParsingNumericNode() throws Exception { + Map tree = new HashMap<>(); + long seconds = Integer.MAX_VALUE + 10000L; + LongNode node = new LongNode(seconds); + tree.put("key", node); + + Date date = deserializer.getDateFromSeconds(tree, "key"); + assertThat(date, is(notNullValue())); + assertThat(date.getTime(), is(seconds * 1000)); + assertThat(date.getTime(), is(2147493647L * 1000)); + } + @Test public void shouldGetNullStringWhenParsingNullNode() throws Exception { Map tree = new HashMap<>(); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java index e49e1e53..ebdd5864 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java @@ -14,8 +14,7 @@ import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; public class PayloadSerializerTest { @@ -117,6 +116,26 @@ public void shouldSerializeCustomDateInSeconds() throws Exception { assertThat(writer.toString(), is(equalTo("{\"birthdate\":1478874}"))); } + @Test + public void shouldSerializeDatesUsingLong() throws Exception { + long secs = Integer.MAX_VALUE + 10000L; + Date date = new Date(secs * 1000L); + Map claims = new HashMap(); + claims.put("iat", date); + claims.put("nbf", date); + claims.put("exp", date); + claims.put("ctm", date); + ClaimsHolder holder = new ClaimsHolder(claims); + serializer.serialize(holder, jsonGenerator, serializerProvider); + jsonGenerator.flush(); + + String json = writer.toString(); + assertThat(json, containsString("\"iat\":2147493647")); + assertThat(json, containsString("\"nbf\":2147493647")); + assertThat(json, containsString("\"exp\":2147493647")); + assertThat(json, containsString("\"ctm\":2147493647")); + } + @Test public void shouldSerializeStrings() throws Exception { ClaimsHolder holder = holderFor("name", "Auth0 Inc"); From 2ed581e360b054ab56ff23d09ab25736bab904c9 Mon Sep 17 00:00:00 2001 From: mvetsch Date: Fri, 24 Feb 2017 15:25:50 +0100 Subject: [PATCH 021/355] Promote the "Verify Token" section (#139) [skip ci] --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 37e151bf..da1006f7 100644 --- a/README.md +++ b/README.md @@ -46,20 +46,6 @@ The library implements JWT Verification and Signing using the following algorith ## Usage -### Decode a Token - -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - JWT jwt = JWT.decode(token); -} catch (JWTDecodeException exception){ - //Invalid token -} -``` - -If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. - - ### Create and Sign a Token You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the Algorithm instance. @@ -164,6 +150,20 @@ Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock); ``` +### Decode a Token + +```java +String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; +try { + JWT jwt = JWT.decode(token); +} catch (JWTDecodeException exception){ + //Invalid token +} +``` + +If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. + + ### Header Claims #### Algorithm ("alg") From 16050be83b4f4a5d357c97e061d1c860e654d8d3 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 16:43:49 -0300 Subject: [PATCH 022/355] set typ=JWT after signing the token --- .../main/java/com/auth0/jwt/JWTCreator.java | 15 ++++++++------- .../java/com/auth0/jwt/JWTCreatorTest.java | 18 +++++++++--------- lib/src/test/java/com/auth0/jwt/JWTTest.java | 18 +++++++++--------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 4ab89f34..06f149f6 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -74,7 +74,7 @@ public Builder withHeader(Map headerClaims) { } /** - * Add a specific Issuer ("iss") claim. + * Add a specific Issuer ("iss") claim to the Payload. * * @param issuer the Issuer value. * @return this same Builder instance. @@ -85,7 +85,7 @@ public Builder withIssuer(String issuer) { } /** - * Add a specific Subject ("sub") claim. + * Add a specific Subject ("sub") claim to the Payload. * * @param subject the Subject value. * @return this same Builder instance. @@ -96,7 +96,7 @@ public Builder withSubject(String subject) { } /** - * Add a specific Audience ("aud") claim. + * Add a specific Audience ("aud") claim to the Payload. * * @param audience the Audience value. * @return this same Builder instance. @@ -107,7 +107,7 @@ public Builder withAudience(String... audience) { } /** - * Add a specific Expires At ("exp") claim. + * Add a specific Expires At ("exp") claim to the Payload. * * @param expiresAt the Expires At value. * @return this same Builder instance. @@ -118,7 +118,7 @@ public Builder withExpiresAt(Date expiresAt) { } /** - * Add a specific Not Before ("nbf") claim. + * Add a specific Not Before ("nbf") claim to the Payload. * * @param notBefore the Not Before value. * @return this same Builder instance. @@ -129,7 +129,7 @@ public Builder withNotBefore(Date notBefore) { } /** - * Add a specific Issued At ("iat") claim. + * Add a specific Issued At ("iat") claim to the Payload. * * @param issuedAt the Issued At value. * @return this same Builder instance. @@ -140,7 +140,7 @@ public Builder withIssuedAt(Date issuedAt) { } /** - * Add a specific JWT Id ("jti") claim. + * Add a specific JWT Id ("jti") claim to the Payload. * * @param jwtId the Token Id value. * @return this same Builder instance. @@ -261,6 +261,7 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea throw new IllegalArgumentException("The Algorithm cannot be null."); } headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); + headerClaims.put(PublicClaims.TYPE, "JWT"); return new JWTCreator(algorithm, headerClaims, payloadClaims).sign(); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 2bb47989..d3b18dd1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -36,7 +36,7 @@ public void shouldAddHeader() throws Exception { .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[0], is("eyJhbGciOiJIUzI1NiIsImFzZCI6MTIzfQ")); + assertThat(TokenUtils.splitToken(signed)[0], is("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImFzZCI6MTIzfQ")); } @Test @@ -134,7 +134,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception { .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[0], is("eyJhbGciOiJIUzI1NiJ9")); + assertThat(TokenUtils.splitToken(signed)[0], is("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")); } @Test @@ -158,7 +158,7 @@ public void shouldAcceptCustomClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() .withClaim("name", "value") .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidmFsdWUifQ.4qDWJcNQHDVDW1iAcIgZNiu-qqJQ0RIq8X3ETijBx5k"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidmFsdWUifQ.eR3DUeX142NjueZjkqCn_NqxJpb5k-Y55Oo0N-ap3rI"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -169,7 +169,7 @@ public void shouldAcceptCustomClaimOfTypeInteger() throws Exception { String jwt = JWTCreator.init() .withClaim("name", 123) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxMjN9.5i6ga8YMteicIeZrFZgJyW4OnI_2jpMaUXcDt-_jme4"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxMjN9.7Diqx9FPPuaw9ESwkZOHL2BARjqQz00qrHYOm0lKcgQ"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -180,7 +180,7 @@ public void shouldAcceptCustomClaimOfTypeDouble() throws Exception { String jwt = JWTCreator.init() .withClaim("name", 23.45) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoyMy40NX0.aFNlMk3WiikukJq1jo4Tf8ztR180wjTfSpqec0xKKqU"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoyMy40NX0.VwOI-xjYFthgT43b9EYcaOSIpGSD6PVLSCPuGzDuEnQ"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -191,7 +191,7 @@ public void shouldAcceptCustomClaimOfTypeBoolean() throws Exception { String jwt = JWTCreator.init() .withClaim("name", true) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp0cnVlfQ.jseAYuhVmT1boYrHQfn9wXmomWq_tdGfphLtG_2tj_M"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp0cnVlfQ.8L_Td4EtEAUuQeNCU0fuJEu78SS8K3Y5OOkFzYA81g8"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -203,7 +203,7 @@ public void shouldAcceptCustomClaimOfTypeDate() throws Exception { String jwt = JWTCreator.init() .withClaim("name", date) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxNDc4ODkxNTIxfQ.ZU1B1pDLYoJZhWD8h3_QsK5dViolxvL5Q43Yz9QIxL4"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxNDc4ODkxNTIxfQ.0esDU87VaYbx6KQDWhFrRPNzq3rl3vcHO8T21fao28U"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -214,7 +214,7 @@ public void shouldAcceptCustomArrayClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() .withArrayClaim("name", new String[]{"text", "123", "true"}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.lxM8EcmK1uSZRAPd0HUhXGZJdauRmZmLjoeqz4J9yAA"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.TTP2tJjVdoOzKfIgDcn_MSP7XQpafeVCKVNE2Y3-0Hk"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); @@ -225,7 +225,7 @@ public void shouldAcceptCustomArrayClaimOfTypeInteger() throws Exception { String jwt = JWTCreator.init() .withArrayClaim("name", new Integer[]{1, 2, 3}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.1AdYaNBWR8lPB0yOxUtnQjuOU7tzD4LWz2AWrziPUqA"; assertThat(jwt, is(notNullValue())); assertThat(jwt, is(token)); diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index d54af0d7..717ec27e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -353,7 +353,7 @@ public void shouldGetCustomClaims() throws Exception { @Test public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJIUzI1NiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30."; String signed = JWT.create().sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); @@ -366,7 +366,7 @@ public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJIUzM4NCJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30."; String signed = JWT.create().sign(Algorithm.HMAC384("secret")); assertThat(signed, is(notNullValue())); @@ -379,7 +379,7 @@ public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJIUzUxMiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30."; String signed = JWT.create().sign(Algorithm.HMAC512("secret")); assertThat(signed, is(notNullValue())); @@ -392,7 +392,7 @@ public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJSUzI1NiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e30."; String signed = JWT.create().sign(Algorithm.RSA256((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); @@ -405,7 +405,7 @@ public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJSUzM4NCJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.e30."; String signed = JWT.create().sign(Algorithm.RSA384((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); @@ -418,7 +418,7 @@ public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJSUzUxMiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.e30."; String signed = JWT.create().sign(Algorithm.RSA512((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); @@ -431,7 +431,7 @@ public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJFUzI1NiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.e30."; String signed = JWT.create().sign(Algorithm.ECDSA256((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"))); assertThat(signed, is(notNullValue())); @@ -444,7 +444,7 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJFUzM4NCJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.e30."; String signed = JWT.create().sign(Algorithm.ECDSA384((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_384, "EC"))); assertThat(signed, is(notNullValue())); @@ -457,7 +457,7 @@ public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA512SignedToken() throws Exception { - String headerAndPayload = "eyJhbGciOiJFUzUxMiJ9.e30."; + String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.e30."; String signed = JWT.create().sign(Algorithm.ECDSA512((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_512, "EC"))); assertThat(signed, is(notNullValue())); From 970fb3cdc5bd13bb6bd057d0351439e77e7e9fb5 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 16:45:12 -0300 Subject: [PATCH 023/355] add setter for the headers Key Id value --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 11 +++++++++++ lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 06f149f6..abb538fa 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -73,6 +73,17 @@ public Builder withHeader(Map headerClaims) { return this; } + /** + * Add a specific Key Id ("kid") claim to the Header. + * + * @param keyId the Key Id value. + * @return this same Builder instance. + */ + public Builder withKeyId(String keyId) { + this.headerClaims.put(PublicClaims.KEY_ID, keyId); + return this; + } + /** * Add a specific Issuer ("iss") claim to the Payload. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index d3b18dd1..a123d8e8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -39,6 +39,16 @@ public void shouldAddHeader() throws Exception { assertThat(TokenUtils.splitToken(signed)[0], is("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImFzZCI6MTIzfQ")); } + @Test + public void shouldAddKeyId() throws Exception { + String signed = JWTCreator.init() + .withKeyId("56a8bd44da435300010000015f5ed") + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + assertThat(TokenUtils.splitToken(signed)[0], is("eyJraWQiOiI1NmE4YmQ0NGRhNDM1MzAwMDEwMDAwMDE1ZjVlZCIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0")); + } + @Test public void shouldAddIssuer() throws Exception { String signed = JWTCreator.init() From c9090a06ad4be2ea8aa1e9fdcbaac97feb0b38f2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 17:57:59 -0300 Subject: [PATCH 024/355] add Header claims setter info to the README.md file --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da1006f7..9a27f2a1 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,18 @@ Additional Claims defined in the token's Header can be obtained by calling `getH Claim claim = jwt.getHeaderClaim("owner"); ``` +When creating a Token with the `JWT.create()` you can specify header Claims by calling `withHeader()` and passing both the map of claims. + +```java +Map headerClaims = new HashMap(); +headerclaims.put("owner", "auth0"); +JWT.create() + .withHeader(headerClaims) + .sign(Algorithm.HMAC256("secret")); +``` + +> The `alg` and `typ` values will always be included in the Header after the signing process. + ### Payload Claims @@ -273,14 +285,14 @@ Additional Claims defined in the token's Payload can be obtained by calling `get Map claims = jwt.getClaims(); //Key is the Claim name Claim claim = claims.get("isAdmin"); ``` -al + or ```java Claim claim = jwt.getClaim("isAdmin"); ``` -When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. +When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. ```java JWT.create() From d5ce251fafea16ab88d000c8b78794fb39614471 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Jan 2017 19:05:10 -0300 Subject: [PATCH 025/355] fix header tests --- .../main/java/com/auth0/jwt/JWTCreator.java | 3 + .../java/com/auth0/jwt/JWTCreatorTest.java | 55 ++++--- lib/src/test/java/com/auth0/jwt/JWTTest.java | 83 +++++++---- .../test/java/com/auth0/jwt/JsonMatcher.java | 136 ++++++++++++++++++ 4 files changed, 232 insertions(+), 45 deletions(-) create mode 100644 lib/src/test/java/com/auth0/jwt/JsonMatcher.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index abb538fa..23d87dc4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -7,7 +7,9 @@ import com.auth0.jwt.impl.PayloadSerializer; import com.auth0.jwt.impl.PublicClaims; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.commons.codec.binary.Base64; @@ -33,6 +35,7 @@ private JWTCreator(Algorithm algorithm, Map headerClaims, Map header = new HashMap(); header.put("asd", 123); String signed = JWTCreator.init() @@ -36,7 +38,9 @@ public void shouldAddHeader() throws Exception { .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[0], is("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImFzZCI6MTIzfQ")); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("asd", 123)); } @Test @@ -46,7 +50,9 @@ public void shouldAddKeyId() throws Exception { .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[0], is("eyJraWQiOiI1NmE4YmQ0NGRhNDM1MzAwMDEwMDAwMDE1ZjVlZCIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0")); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed")); } @Test @@ -144,7 +150,20 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception { .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[0], is("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); + } + + @Test + public void shouldSetCorrectTypeInTheHeader() throws Exception { + String signed = JWTCreator.init() + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); } @Test @@ -168,10 +187,10 @@ public void shouldAcceptCustomClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() .withClaim("name", "value") .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidmFsdWUifQ.eR3DUeX142NjueZjkqCn_NqxJpb5k-Y55Oo0N-ap3rI"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjoidmFsdWUifQ")); } @Test @@ -179,10 +198,10 @@ public void shouldAcceptCustomClaimOfTypeInteger() throws Exception { String jwt = JWTCreator.init() .withClaim("name", 123) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxMjN9.7Diqx9FPPuaw9ESwkZOHL2BARjqQz00qrHYOm0lKcgQ"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjoxMjN9")); } @Test @@ -190,10 +209,10 @@ public void shouldAcceptCustomClaimOfTypeDouble() throws Exception { String jwt = JWTCreator.init() .withClaim("name", 23.45) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoyMy40NX0.VwOI-xjYFthgT43b9EYcaOSIpGSD6PVLSCPuGzDuEnQ"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjoyMy40NX0")); } @Test @@ -201,10 +220,10 @@ public void shouldAcceptCustomClaimOfTypeBoolean() throws Exception { String jwt = JWTCreator.init() .withClaim("name", true) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp0cnVlfQ.8L_Td4EtEAUuQeNCU0fuJEu78SS8K3Y5OOkFzYA81g8"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjp0cnVlfQ")); } @Test @@ -213,10 +232,10 @@ public void shouldAcceptCustomClaimOfTypeDate() throws Exception { String jwt = JWTCreator.init() .withClaim("name", date) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxNDc4ODkxNTIxfQ.0esDU87VaYbx6KQDWhFrRPNzq3rl3vcHO8T21fao28U"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjoxNDc4ODkxNTIxfQ")); } @Test @@ -224,10 +243,10 @@ public void shouldAcceptCustomArrayClaimOfTypeString() throws Exception { String jwt = JWTCreator.init() .withArrayClaim("name", new String[]{"text", "123", "true"}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.TTP2tJjVdoOzKfIgDcn_MSP7XQpafeVCKVNE2Y3-0Hk"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19")); } @Test @@ -235,9 +254,9 @@ public void shouldAcceptCustomArrayClaimOfTypeInteger() throws Exception { String jwt = JWTCreator.init() .withArrayClaim("name", new Integer[]{1, 2, 3}) .sign(Algorithm.HMAC256("secret")); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.1AdYaNBWR8lPB0yOxUtnQjuOU7tzD4LWz2AWrziPUqA"; assertThat(jwt, is(notNullValue())); - assertThat(jwt, is(token)); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 717ec27e..31df4494 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -3,12 +3,14 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; +import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.nio.charset.StandardCharsets; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.util.Date; @@ -353,11 +355,14 @@ public void shouldGetCustomClaims() throws Exception { @Test public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30."; - String signed = JWT.create().sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.HMAC256("secret")) .build(); @@ -366,11 +371,14 @@ public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30."; - String signed = JWT.create().sign(Algorithm.HMAC384("secret")); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS384")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.HMAC384("secret")) .build(); @@ -379,11 +387,14 @@ public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30."; - String signed = JWT.create().sign(Algorithm.HMAC512("secret")); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS512")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.HMAC512("secret")) .build(); @@ -392,11 +403,14 @@ public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e30."; - String signed = JWT.create().sign(Algorithm.RSA256((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS256")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.RSA256((RSAKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_RSA, "RSA"))) .build(); @@ -405,11 +419,14 @@ public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.e30."; - String signed = JWT.create().sign(Algorithm.RSA384((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS384")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.RSA384((RSAKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_RSA, "RSA"))) .build(); @@ -418,11 +435,14 @@ public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.e30."; - String signed = JWT.create().sign(Algorithm.RSA512((RSAKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS512")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.RSA512((RSAKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_RSA, "RSA"))) .build(); @@ -431,11 +451,14 @@ public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.e30."; - String signed = JWT.create().sign(Algorithm.ECDSA256((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.ECDSA256((ECKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_256, "EC"))) .build(); @@ -444,11 +467,14 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.e30."; - String signed = JWT.create().sign(Algorithm.ECDSA384((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_384, "EC"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES384")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.ECDSA384((ECKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_384, "EC"))) .build(); @@ -457,11 +483,14 @@ public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA512SignedToken() throws Exception { - String headerAndPayload = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.e30."; - String signed = JWT.create().sign(Algorithm.ECDSA512((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_512, "EC"))); assertThat(signed, is(notNullValue())); - assertThat(signed, startsWith(headerAndPayload)); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES512")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); JWTVerifier verified = JWT.require(Algorithm.ECDSA512((ECKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_512, "EC"))) .build(); diff --git a/lib/src/test/java/com/auth0/jwt/JsonMatcher.java b/lib/src/test/java/com/auth0/jwt/JsonMatcher.java new file mode 100644 index 00000000..f03547d4 --- /dev/null +++ b/lib/src/test/java/com/auth0/jwt/JsonMatcher.java @@ -0,0 +1,136 @@ +package com.auth0.jwt; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import java.lang.reflect.Array; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class JsonMatcher extends TypeSafeDiagnosingMatcher { + + private final String entry; + private final String key; + private final Matcher matcher; + + private JsonMatcher(String key, Object value, Matcher valueMatcher) { + this.key = key; + this.matcher = valueMatcher; + if (value != null) { + String stringValue = objectToString(value); + entry = getStringKey(key) + stringValue; + } else { + entry = null; + } + } + + @Override + protected boolean matchesSafely(String item, Description mismatchDescription) { + if (item == null) { + mismatchDescription.appendText("JSON was null"); + return false; + } + if (matcher != null) { + if (!matcher.matches(item)) { + matcher.describeMismatch(item, mismatchDescription); + return false; + } + if (!item.contains(getStringKey(key))) { + mismatchDescription.appendText("JSON didn't contained the key ").appendValue(key); + return false; + } + } + if (entry != null && !item.contains(entry)) { + mismatchDescription.appendText("JSON was ").appendValue(item); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + if (matcher == null) { + description.appendText("A JSON with entry ") + .appendValue(entry); + } else { + matcher.describeTo(description); + } + } + + public static JsonMatcher hasEntry(String key, Object value) { + return new JsonMatcher(key, value, null); + } + + public static JsonMatcher hasEntry(String key, Matcher valueMatcher) { + return new JsonMatcher(key, null, valueMatcher); + } + + private String getStringKey(String key) { + return "\"" + key + "\":"; + } + + private String objectToString(Object value) { + String stringValue; + if (value == null) { + stringValue = "null"; + } else if (value instanceof String) { + stringValue = "\"" + value + "\""; + } else if (value instanceof Map) { + stringValue = mapToString((Map) value); + } else if (value instanceof Array) { + stringValue = arrayToString((Object[]) value); + } else if (value instanceof List) { + stringValue = listToString((List) value); + } else { + stringValue = value.toString(); + } + return stringValue; + } + + private String arrayToString(Object[] array) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < array.length; i++) { + Object o = array[i]; + sb.append(objectToString(o)); + if (i + 1 < array.length) { + sb.append(","); + } + } + sb.append("]"); + return sb.toString(); + } + + private String listToString(List list) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + Iterator it = list.iterator(); + while (it.hasNext()) { + Object o = it.next(); + sb.append(objectToString(o)); + if (it.hasNext()) { + sb.append(","); + } + } + sb.append("]"); + return sb.toString(); + } + + private String mapToString(Map map) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + Iterator> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + sb.append("\"" + e.getKey() + "\":" + objectToString(e.getValue())); + if (it.hasNext()) { + sb.append(","); + } + } + sb.append("}"); + return sb.toString(); + } +} \ No newline at end of file From d5846c0d22290dda827dc053b22776135887e8cf Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 3 Mar 2017 18:14:57 -0300 Subject: [PATCH 026/355] add circle.yml file --- circle.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..07d14f09 --- /dev/null +++ b/circle.yml @@ -0,0 +1,20 @@ +# +# Build configuration for Circle CI +# + +machine: + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + java: + version: oraclejdk7 + +dependencies: + cache_directories: + - ~/.gradle + +test: + override: + - ./gradlew clean check jacocoTestReport --continue --console=plain + post: + - bash <(curl -s https://codecov.io/bash) From 197091e52a45ca744940ddde417a10aa13addb57 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 13 Mar 2017 16:46:15 -0300 Subject: [PATCH 027/355] accept blanks, new line and carriage returns on json --- .../java/com/auth0/jwt/impl/JWTParser.java | 2 +- .../java/com/auth0/jwt/JWTDecoderTest.java | 4 ++-- .../com/auth0/jwt/impl/JWTParserTest.java | 20 +++---------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index b633e1b2..1efb825e 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -50,7 +50,7 @@ static ObjectMapper getDefaultObjectMapper() { @SuppressWarnings("WeakerAccess") T convertFromJSON(String json, Class tClazz) throws JWTDecodeException { JWTDecodeException exception = new JWTDecodeException(String.format("The string '%s' doesn't have a valid JSON format.", json)); - if (json == null || !json.startsWith("{") || !json.endsWith("}")) { + if (json == null) { throw exception; } try { diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index a4ab4376..792a3735 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -47,7 +47,7 @@ public void shouldThrowIfMoreThan3Parts() throws Exception { @Test public void shouldThrowIfPayloadHasInvalidJSONFormat() throws Exception { String validJson = "{}"; - String invalidJson = "{}}{"; + String invalidJson = "}{"; exception.expect(JWTDecodeException.class); exception.expectMessage(String.format("The string '%s' doesn't have a valid JSON format.", invalidJson)); customJWT(validJson, invalidJson, "signature"); @@ -56,7 +56,7 @@ public void shouldThrowIfPayloadHasInvalidJSONFormat() throws Exception { @Test public void shouldThrowIfHeaderHasInvalidJSONFormat() throws Exception { String validJson = "{}"; - String invalidJson = "{}}{"; + String invalidJson = "}{"; exception.expect(JWTDecodeException.class); exception.expectMessage(String.format("The string '%s' doesn't have a valid JSON format.", invalidJson)); customJWT(invalidJson, validJson, "signature"); diff --git a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java index 09ab42fc..1c990fcb 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java @@ -11,13 +11,12 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.io.IOException; - import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class JWTParserTest { @@ -45,19 +44,6 @@ public void shouldAddDeserializers() throws Exception { verify(mapper).registerModule(any(Module.class)); } - @Test - public void shouldThrowOnReadValueException() throws Exception { - String jsonPayload = "{}"; - exception.expect(JWTDecodeException.class); - exception.expectMessage(String.format("The string '%s' doesn't have a valid JSON format.", jsonPayload)); - - ObjectMapper mapper = mock(ObjectMapper.class); - when(mapper.readValue(eq(jsonPayload), eq(Object.class))).thenThrow(IOException.class); - JWTParser parser = new JWTParser(mapper); - - parser.convertFromJSON(jsonPayload, Object.class); - } - @Test public void shouldParsePayload() throws Exception { ObjectMapper mapper = mock(ObjectMapper.class); @@ -96,7 +82,7 @@ public void shouldThrowOnInvalidHeader() throws Exception { @Test public void shouldConvertFromValidJSON() throws Exception { - String json = "{}"; + String json = "\r\n { \r\n } \r\n"; Object object = parser.convertFromJSON(json, Object.class); assertThat(object, is(notNullValue())); } From e4f2aeb43ccb9ab998dfab51d9729e10d76fda70 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 13 Mar 2017 16:26:04 -0300 Subject: [PATCH 028/355] change JWT.decode return type to DecodedJWT --- README.md | 6 +++--- lib/src/main/java/com/auth0/jwt/JWT.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9a27f2a1..f430a0f2 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256("secret")) .withIssuer("auth0") .build(); //Reusable verifier instance - JWT jwt = verifier.verify(token); + DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims } @@ -105,7 +105,7 @@ try { JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) .withIssuer("auth0") .build(); //Reusable verifier instance - JWT jwt = verifier.verify(token); + DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims } @@ -155,7 +155,7 @@ JWTVerifier verifier = verification.build(clock); ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { - JWT jwt = JWT.decode(token); + DecodedJWT jwt = JWT.decode(token); } catch (JWTDecodeException exception){ //Invalid token } diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index c8f09c4d..6547f8d9 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -9,15 +9,15 @@ public abstract class JWT implements DecodedJWT { /** - * Decode a given JWT token. + * Decode a given Json Web Token. *

* Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. - * @return a decoded token. + * @return a decoded JWT. * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. */ - public static JWT decode(String token) throws JWTDecodeException { + public static DecodedJWT decode(String token) throws JWTDecodeException { return new JWTDecoder(token); } @@ -33,9 +33,9 @@ public static Verification require(Algorithm algorithm) { } /** - * Returns a JWT builder used to create and sign jwt tokens + * Returns a Json Web Token builder used to create and sign tokens * - * @return a jwt token builder. + * @return a token builder. */ public static JWTCreator.Builder create() { return JWTCreator.init(); From 4f8f54a8f78c827c79104204022be9607df6f1f9 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 10 Mar 2017 15:57:32 -0300 Subject: [PATCH 029/355] refactor Algorithm to accept both keys --- .../com/auth0/jwt/algorithms/Algorithm.java | 114 +++++++- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 39 +-- .../auth0/jwt/algorithms/RSAAlgorithm.java | 42 +-- .../auth0/jwt/algorithms/AlgorithmTest.java | 274 +++++++++++++++--- .../jwt/algorithms/ECDSAAlgorithmTest.java | 138 ++++++--- .../jwt/algorithms/RSAAlgorithmTest.java | 125 +++++--- 6 files changed, 579 insertions(+), 153 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index b74b1aa3..589210cb 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -4,12 +4,14 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import java.io.UnsupportedEncodingException; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.*; /** * The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token. */ +@SuppressWarnings("WeakerAccess") public abstract class Algorithm { private final String name; @@ -21,9 +23,13 @@ public abstract class Algorithm { * @param key the key to use in the verify or signing instance. * @return a valid RSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #RSA256(RSAPublicKey, RSAPrivateKey)} */ + @Deprecated public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { - return new RSAAlgorithm("RS256", "SHA256withRSA", key); + RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + return RSA256(publicKey, privateKey); } /** @@ -32,9 +38,13 @@ public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { * @param key the key to use in the verify or signing instance. * @return a valid RSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #RSA384(RSAPublicKey, RSAPrivateKey)} */ + @Deprecated public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { - return new RSAAlgorithm("RS384", "SHA384withRSA", key); + RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + return RSA384(publicKey, privateKey); } /** @@ -43,9 +53,49 @@ public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { * @param key the key to use in the verify or signing instance. * @return a valid RSA512 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #RSA512(RSAPublicKey, RSAPrivateKey)} */ + @Deprecated public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { - return new RSAAlgorithm("RS512", "SHA512withRSA", key); + RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + return RSA512(publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid RSA256 Algorithm. + * @throws IllegalArgumentException if both provided Keys are null. + */ + public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + return new RSAAlgorithm("RS256", "SHA256withRSA", publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid RSA384 Algorithm. + * @throws IllegalArgumentException if both provided Keys are null. + */ + public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + return new RSAAlgorithm("RS384", "SHA384withRSA", publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid RSA512 Algorithm. + * @throws IllegalArgumentException if both provided Keys are null. + */ + public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + return new RSAAlgorithm("RS512", "SHA512withRSA", publicKey, privateKey); } /** @@ -123,9 +173,13 @@ public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { * @param key the key to use in the verify or signing instance. * @return a valid ECDSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} */ + @Deprecated public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, key); + ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + return ECDSA256(publicKey, privateKey); } /** @@ -134,9 +188,13 @@ public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { * @param key the key to use in the verify or signing instance. * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} */ + @Deprecated public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, key); + ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + return ECDSA384(publicKey, privateKey); } /** @@ -145,9 +203,49 @@ public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { * @param key the key to use in the verify or signing instance. * @return a valid ECDSA512 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} */ + @Deprecated public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, key); + ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + return ECDSA512(publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid ECDSA256 Algorithm. + * @throws IllegalArgumentException if the provided Key is null. + */ + public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid ECDSA384 Algorithm. + * @throws IllegalArgumentException if the provided Key is null. + */ + public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, publicKey, privateKey); + } + + /** + * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid ECDSA512 Algorithm. + * @throws IllegalArgumentException if the provided Key is null. + */ + public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, publicKey, privateKey); } public static Algorithm none() { 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 e24bf098..cc47f86b 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -5,46 +5,51 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.SignatureException; -import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; class ECDSAAlgorithm extends Algorithm { + private final ECPublicKey publicKey; + private final ECPrivateKey privateKey; private final CryptoHelper crypto; private final int ecNumberSize; - private final ECKey key; - ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECKey key) throws IllegalArgumentException { + //Visible for testing + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { super(id, algorithm); - if (key == null) { - throw new IllegalArgumentException("The ECKey cannot be null"); + if (publicKey == null && privateKey == null) { + throw new IllegalArgumentException("Both provided Keys cannot be null."); } + this.publicKey = publicKey; + this.privateKey = privateKey; this.ecNumberSize = ecNumberSize; - this.key = key; this.crypto = crypto; } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECKey key) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, ecNumberSize, key); + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + this(new CryptoHelper(), id, algorithm, ecNumberSize, publicKey, privateKey); } - ECKey getKey() { - return key; + ECPublicKey getPublicKey() { + return publicKey; + } + + ECPrivateKey getPrivateKey() { + return privateKey; } @Override public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { - if (!(key instanceof ECPublicKey)) { - throw new IllegalArgumentException("The given ECKey is not an ECPublicKey."); + if (publicKey == null) { + throw new IllegalArgumentException("The given Public Key is null."); } if (!isDERSignature(signatureBytes)) { signatureBytes = JOSEToDER(signatureBytes); } - boolean valid = crypto.verifySignatureFor(getDescription(), (ECPublicKey) key, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); @@ -57,10 +62,10 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (!(key instanceof ECPrivateKey)) { - throw new IllegalArgumentException("The given ECKey is not a ECPrivateKey."); + if (privateKey == null) { + throw new IllegalArgumentException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), (PrivateKey) key, contentBytes); + return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { throw new SignatureGenerationException(this, e); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index cc217515..2ab08331 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -3,40 +3,48 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; -import java.security.*; -import java.security.interfaces.RSAKey; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; class RSAAlgorithm extends Algorithm { - private final RSAKey key; + private final RSAPublicKey publicKey; + private final RSAPrivateKey privateKey; private final CryptoHelper crypto; - RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKey key) throws IllegalArgumentException { + //Visible for testing + RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { super(id, algorithm); - if (key == null) { - throw new IllegalArgumentException("The RSAKey cannot be null"); + if (publicKey == null && privateKey == null) { + throw new IllegalArgumentException("Both provided Keys cannot be null."); } - this.key = key; + this.publicKey = publicKey; + this.privateKey = privateKey; this.crypto = crypto; } - RSAAlgorithm(String id, String algorithm, RSAKey key) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, key); + RSAAlgorithm(String id, String algorithm, RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + this(new CryptoHelper(), id, algorithm, publicKey, privateKey); } - RSAKey getKey() { - return key; + RSAPublicKey getPublicKey() { + return publicKey; + } + + RSAPrivateKey getPrivateKey() { + return privateKey; } @Override public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { - if (!(key instanceof PublicKey)) { - throw new IllegalArgumentException("The given RSAKey is not a RSAPublicKey."); + if (publicKey == null) { + throw new IllegalArgumentException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), (RSAPublicKey) key, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } @@ -48,10 +56,10 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (!(key instanceof PrivateKey)) { - throw new IllegalArgumentException("The given RSAKey is not a RSAPrivateKey."); + if (privateKey == null) { + throw new IllegalArgumentException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), (RSAPrivateKey) key, contentBytes); + return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { throw new SignatureGenerationException(this, e); } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index 73b23dbb..0b0c6249 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -5,12 +5,12 @@ import org.junit.rules.ExpectedException; import java.nio.charset.StandardCharsets; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; +import java.security.interfaces.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; public class AlgorithmTest { @@ -19,7 +19,7 @@ public class AlgorithmTest { @Test - public void shouldThrowHMAC256VerificationWithNullSecretBytes() throws Exception { + public void shouldThrowHMAC256InstanceWithNullSecretBytes() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); byte[] secret = null; @@ -27,7 +27,7 @@ public void shouldThrowHMAC256VerificationWithNullSecretBytes() throws Exception } @Test - public void shouldThrowHMAC384VerificationWithNullSecretBytes() throws Exception { + public void shouldThrowHMAC384InstanceWithNullSecretBytes() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); byte[] secret = null; @@ -35,7 +35,7 @@ public void shouldThrowHMAC384VerificationWithNullSecretBytes() throws Exception } @Test - public void shouldThrowHMAC512VerificationWithNullSecretBytes() throws Exception { + public void shouldThrowHMAC512InstanceWithNullSecretBytes() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); byte[] secret = null; @@ -43,7 +43,7 @@ public void shouldThrowHMAC512VerificationWithNullSecretBytes() throws Exception } @Test - public void shouldThrowHMAC256VerificationWithNullSecret() throws Exception { + public void shouldThrowHMAC256InstanceWithNullSecret() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); String secret = null; @@ -51,7 +51,7 @@ public void shouldThrowHMAC256VerificationWithNullSecret() throws Exception { } @Test - public void shouldThrowHMAC384VerificationWithNullSecret() throws Exception { + public void shouldThrowHMAC384InstanceWithNullSecret() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); String secret = null; @@ -59,7 +59,7 @@ public void shouldThrowHMAC384VerificationWithNullSecret() throws Exception { } @Test - public void shouldThrowHMAC512VerificationWithNullSecret() throws Exception { + public void shouldThrowHMAC512InstanceWithNullSecret() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Secret cannot be null"); String secret = null; @@ -67,47 +67,89 @@ public void shouldThrowHMAC512VerificationWithNullSecret() throws Exception { } @Test - public void shouldThrowRSA256VerificationWithNullPublicKey() throws Exception { + public void shouldThrowRSA256InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The RSAKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.RSA256(null); } @Test - public void shouldThrowRSA384VerificationWithNullPublicKey() throws Exception { + public void shouldThrowRSA256InstanceWithNullKeys() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The RSAKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.RSA256(null, null); + } + + @Test + public void shouldThrowRSA384InstanceWithNullKey() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.RSA384(null); } @Test - public void shouldThrowRSA512VerificationWithNullPublicKey() throws Exception { + public void shouldThrowRSA384InstanceWithNullKeys() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The RSAKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.RSA384(null, null); + } + + @Test + public void shouldThrowRSA512InstanceWithNullKey() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.RSA512(null); } @Test - public void shouldThrowECDSA256VerificationWithNullPublicKey() throws Exception { + public void shouldThrowRSA512InstanceWithNullKeys() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.RSA512(null, null); + } + + @Test + public void shouldThrowECDSA256InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The ECKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.ECDSA256(null); } @Test - public void shouldThrowECDSA384VerificationWithNullPublicKey() throws Exception { + public void shouldThrowECDSA256InstanceWithNullKeys() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The ECKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.ECDSA256(null, null); + } + + @Test + public void shouldThrowECDSA384InstanceWithNullKey() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.ECDSA384(null); } @Test - public void shouldThrowECDSA512VerificationWithNullPublicKey() throws Exception { + public void shouldThrowECDSA384InstanceWithNullKeys() throws Exception { exception.expect(IllegalArgumentException.class); - exception.expectMessage("The ECKey cannot be null"); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.ECDSA384(null, null); + } + + @Test + public void shouldThrowECDSA512InstanceWithNullKey() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); Algorithm.ECDSA512(null); } + @Test + public void shouldThrowECDSA512InstanceWithNullKeys() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Both provided Keys cannot be null."); + Algorithm.ECDSA512(null, null); + } + @Test public void shouldCreateHMAC256AlgorithmWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); @@ -175,75 +217,231 @@ public void shouldCreateHMAC512AlgorithmWithString() throws Exception { } @Test - public void shouldCreateRSA256Algorithm() throws Exception { - RSAKey key = mock(RSAKey.class); + public void shouldCreateRSA256AlgorithmWithPublicKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); Algorithm algorithm = Algorithm.RSA256(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withRSA")); assertThat(algorithm.getName(), is("RS256")); - assertThat(((RSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test - public void shouldCreateRSA384Algorithm() throws Exception { - RSAKey key = mock(RSAKey.class); + public void shouldCreateRSA256AlgorithmWithPrivateKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); + Algorithm algorithm = Algorithm.RSA256(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withRSA")); + assertThat(algorithm.getName(), is("RS256")); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateRSA256Algorithm() throws Exception { + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withRSA")); + assertThat(algorithm.getName(), is("RS256")); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateRSA384AlgorithmWithPublicKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); Algorithm algorithm = Algorithm.RSA384(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withRSA")); assertThat(algorithm.getName(), is("RS384")); - assertThat(((RSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test - public void shouldCreateRSA512Algorithm() throws Exception { - RSAKey key = mock(RSAKey.class); + public void shouldCreateRSA384AlgorithmWithPrivateKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); + Algorithm algorithm = Algorithm.RSA384(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withRSA")); + assertThat(algorithm.getName(), is("RS384")); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateRSA384Algorithm() throws Exception { + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = Algorithm.RSA384(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withRSA")); + assertThat(algorithm.getName(), is("RS384")); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateRSA512AlgorithmWithPublicKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); Algorithm algorithm = Algorithm.RSA512(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withRSA")); assertThat(algorithm.getName(), is("RS512")); - assertThat(((RSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test - public void shouldCreateECDSA256Algorithm() throws Exception { - ECKey key = mock(ECKey.class); + public void shouldCreateRSA512AlgorithmWithPrivateKey() throws Exception { + RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); + Algorithm algorithm = Algorithm.RSA512(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withRSA")); + assertThat(algorithm.getName(), is("RS512")); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateRSA512Algorithm() throws Exception { + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = Algorithm.RSA512(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withRSA")); + assertThat(algorithm.getName(), is("RS512")); + assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA256AlgorithmWithPublicKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); Algorithm algorithm = Algorithm.ECDSA256(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); - assertThat(((ECDSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test - public void shouldCreateECDSA384Algorithm() throws Exception { - ECKey key = mock(ECKey.class); + public void shouldCreateECDSA256AlgorithmWithPrivateKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); + Algorithm algorithm = Algorithm.ECDSA256(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256")); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateECDSA256Algorithm() throws Exception { + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = Algorithm.ECDSA256(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256")); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA384AlgorithmWithPublicKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); Algorithm algorithm = Algorithm.ECDSA384(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withECDSA")); assertThat(algorithm.getName(), is("ES384")); - assertThat(((ECDSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test - public void shouldCreateECDSA512Algorithm() throws Exception { - ECKey key = mock(ECKey.class); + public void shouldCreateECDSA384AlgorithmWithPrivateKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); + Algorithm algorithm = Algorithm.ECDSA384(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withECDSA")); + assertThat(algorithm.getName(), is("ES384")); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateECDSA384Algorithm() throws Exception { + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = Algorithm.ECDSA384(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withECDSA")); + assertThat(algorithm.getName(), is("ES384")); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA512AlgorithmWithPublicKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); + Algorithm algorithm = Algorithm.ECDSA512(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withECDSA")); + assertThat(algorithm.getName(), is("ES512")); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); + } + + @Test + public void shouldCreateECDSA512AlgorithmWithPrivateKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); Algorithm algorithm = Algorithm.ECDSA512(key); assertThat(algorithm, is(notNullValue())); assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withECDSA")); assertThat(algorithm.getName(), is("ES512")); - assertThat(((ECDSAAlgorithm) algorithm).getKey(), is(key)); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); + } + + @Test + public void shouldCreateECDSA512Algorithm() throws Exception { + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = Algorithm.ECDSA512(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withECDSA")); + assertThat(algorithm.getName(), is("ES512")); + assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); + assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); } @Test 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 e1c6bd61..b459f6d4 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -16,12 +16,12 @@ import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.isA; 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.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ECDSAAlgorithmTest { @@ -61,6 +61,20 @@ public void shouldPassECDSA256VerificationWithDERSignature() throws Exception { AlgorithmUtils.verify(algorithm, 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + + @Test + public void shouldPassECDSA256VerificationWithDERSignatureWithBothKeys() throws Exception { + 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailECDSA256VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -75,7 +89,7 @@ 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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not an ECPublicKey."))); + 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")); AlgorithmUtils.verify(algorithm, jwt); @@ -139,6 +153,20 @@ public void shouldPassECDSA384VerificationWithDERSignature() throws Exception { AlgorithmUtils.verify(algorithm, 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + + @Test + public void shouldPassECDSA384VerificationWithDERSignatureWithBothKeys() throws Exception { + 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailECDSA384VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -153,7 +181,7 @@ 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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not an ECPublicKey."))); + 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")); AlgorithmUtils.verify(algorithm, jwt); @@ -217,6 +245,20 @@ public void shouldPassECDSA512VerificationWithDERSignature() throws Exception { AlgorithmUtils.verify(algorithm, 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + + @Test + public void shouldPassECDSA512VerificationWithDERSignatureWithBothKeys() throws Exception { + 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")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailECDSA512VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -231,7 +273,7 @@ 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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not an ECPublicKey."))); + 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")); AlgorithmUtils.verify(algorithm, jwt); @@ -291,7 +333,9 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; - Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC")); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, publicKey, privateKey); AlgorithmUtils.verify(algorithm, jwt); } @@ -305,8 +349,9 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; AlgorithmUtils.verify(algorithm, jwt); } @@ -321,8 +366,9 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(InvalidKeyException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; AlgorithmUtils.verify(algorithm, jwt); } @@ -337,8 +383,9 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(SignatureException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; AlgorithmUtils.verify(algorithm, jwt); } @@ -360,12 +407,22 @@ public void shouldDoECDSA256Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoECDSA256SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] contentBytes = String.format("%s.%s", ES256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(contentBytes, signatureBytes); + } + @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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not a ECPrivateKey."))); + 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]); @@ -382,12 +439,22 @@ public void shouldDoECDSA384Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoECDSA384SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + byte[] contentBytes = String.format("%s.%s", ES384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(contentBytes, signatureBytes); + } + @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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not a ECPrivateKey."))); + 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]); @@ -404,12 +471,22 @@ public void shouldDoECDSA512Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoECDSA512SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + byte[] contentBytes = String.format("%s.%s", ES512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(contentBytes, signatureBytes); + } + @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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not a ECPrivateKey."))); + 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]); @@ -425,8 +502,9 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -440,24 +518,9 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); - } - - @Test - public void shouldThrowOnSignWhenUsingPublicKey() 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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given ECKey is not a ECPrivateKey."))); - - CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) - .thenThrow(InvalidKeyException.class); - - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -471,8 +534,9 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(SignatureException.class); - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, key); + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index ea6961fc..15610664 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -16,12 +16,12 @@ import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.isA; 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.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class RSAAlgorithmTest { @@ -41,6 +41,13 @@ public void shouldPassRSA256Verification() throws Exception { AlgorithmUtils.verify(algorithm, jwt); } + @Test + public void shouldPassRSA256VerificationWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailRSA256VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -55,7 +62,7 @@ public void shouldFailRSA256VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPublicKey."))); + exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); AlgorithmUtils.verify(algorithm, jwt); @@ -68,6 +75,13 @@ public void shouldPassRSA384Verification() throws Exception { AlgorithmUtils.verify(algorithm, jwt); } + @Test + public void shouldPassRSA384VerificationWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; + Algorithm algorithm = Algorithm.RSA384((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailRSA384VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -82,7 +96,7 @@ public void shouldFailRSA384VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPublicKey."))); + exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); AlgorithmUtils.verify(algorithm, jwt); @@ -95,6 +109,13 @@ public void shouldPassRSA512Verification() throws Exception { AlgorithmUtils.verify(algorithm, jwt); } + @Test + public void shouldPassRSA512VerificationWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; + Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + AlgorithmUtils.verify(algorithm, jwt); + } + @Test public void shouldFailRSA512VerificationWithInvalidPublicKey() throws Exception { exception.expect(SignatureVerificationException.class); @@ -109,7 +130,7 @@ public void shouldFailRSA512VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPublicKey."))); + exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); AlgorithmUtils.verify(algorithm, jwt); @@ -125,8 +146,9 @@ public void shouldThrowWhenMacAlgorithmDoesNotExists() throws Exception { when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -141,8 +163,9 @@ public void shouldThrowWhenThePublicKeyIsInvalid() throws Exception { when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(InvalidKeyException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -157,8 +180,9 @@ public void shouldThrowWhenTheSignatureIsNotPrepared() throws Exception { when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) .thenThrow(SignatureException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -185,12 +209,26 @@ public void shouldDoRSA256Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoRSA256SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + + byte[] contentBytes = String.format("%s.%s", RS256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String expectedSignature = "ZB-Tr0vLtnf8I9fhSdSjU6HZei5xLYZQ6nZqM5O6Va0W9PgAqgRT7ShI9CjeYulRXPHvVmSl5EQuYuXdBzM0-H_3p_Nsl6tSMy4EyX2kkhEm6T0HhvarTh8CG0PCjn5p6FP5ZxWwhLcmRN70ItP6Z5MMO4CcJh1JrNxR4Fi4xQgt-CK2aVDMFXd-Br5yQiLVx1CX83w28OD9wssW3Rdltl5e66vCef0Ql6Q5I5e5F0nqGYT989a9fkNgLIx2F8k_az5x07BY59FV2SZg59nSiY7TZNjP8ot11Ew7HKRfPXOdh9eKRUVdhcxzqDePhyzKabU8TG5FP0SiWH5qVPfAgw"; + + assertThat(signature, is(notNullValue())); + assertThat(signature, is(expectedSignature)); + algorithm.verify(contentBytes, signatureBytes); + } + @Test public void shouldFailOnRSA256SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPrivateKey."))); + exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); algorithm.sign(new byte[0]); @@ -211,12 +249,26 @@ public void shouldDoRSA384Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoRSA384SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.RSA384((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + + byte[] contentBytes = String.format("%s.%s", RS384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String expectedSignature = "Jx1PaTBnjd_U56MNjifFcY7w9ImDbseg0y8Ijr2pSiA1_wzQb_wy9undaWfzR5YqdIAXvjS8AGuZUAzIoTG4KMgOgdVyYDz3l2jzj6wI-lgqfR5hTy1w1ruMUQ4_wobpdxAiJ4fEbg8Mi_GljOiCO-P1HilxKnpiOJZidR8MQGwTInsf71tOUkK4x5UsdmUueuZbaU-CL5kPnRfXmJj9CcdxZbD9oMlbo23dwkP5BNMrS2LwGGzc9C_-ypxrBIOVilG3WZxcSmuG86LjcZbnL6LBEfph5NmKBgQav147uipb_7umBEr1m2dYiB_9u606n3bcoo3rnsYYK_Xfi1GAEQ"; + + assertThat(signature, is(notNullValue())); + assertThat(signature, is(expectedSignature)); + algorithm.verify(contentBytes, signatureBytes); + } + @Test public void shouldFailOnRSA384SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA384withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPrivateKey."))); + exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); algorithm.sign(new byte[0]); @@ -237,12 +289,26 @@ public void shouldDoRSA512Signing() throws Exception { algorithmVerify.verify(contentBytes, signatureBytes); } + @Test + public void shouldDoRSA512SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + + byte[] contentBytes = String.format("%s.%s", RS512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String expectedSignature = "THIPVYzNZ1Yo_dm0k1UELqV0txs3SzyMopCyHcLXOOdgYXF4MlGvBqu0CFvgSga72Sp5LpuC1Oesj40v_QDsp2GTGDeWnvvcv_eo-b0LPSpmT2h1Ibrmu-z70u2rKf28pkN-AJiMFqi8sit2kMIp1bwIVOovPvMTQKGFmova4Xwb3G526y_PeLlflW1h69hQTIVcI67ACEkAC-byjDnnYIklA-B4GWcggEoFwQRTdRjAUpifA6HOlvnBbZZlUd6KXwEydxVS-eh1odwPjB2_sfbyy5HnLsvNdaniiZQwX7QbwLNT4F72LctYdHHM1QCrID6bgfgYp9Ij9CRX__XDEA"; + + assertThat(signature, is(notNullValue())); + assertThat(signature, is(expectedSignature)); + algorithm.verify(contentBytes, signatureBytes); + } + @Test public void shouldFailOnRSA512SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA512withRSA"); exception.expectCause(isA(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPrivateKey."))); + exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); algorithm.sign(new byte[0]); @@ -258,8 +324,9 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } @@ -273,24 +340,9 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); - algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); - } - - @Test - public void shouldThrowOnSignWhenUsingPublicKey() 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(IllegalArgumentException.class)); - exception.expectCause(hasMessage(is("The given RSAKey is not a RSAPrivateKey."))); - - CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) - .thenThrow(InvalidKeyException.class); - - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPublicKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } @@ -304,8 +356,9 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) .thenThrow(SignatureException.class); - RSAKey key = mock(RSAKey.class, withSettings().extraInterfaces(RSAPrivateKey.class)); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", key); + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } } \ No newline at end of file From bf7e7a721cea4e85d19442aa035044727e4a45c5 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 10 Mar 2017 16:24:25 -0300 Subject: [PATCH 030/355] update readme with algorithm changes --- README.md | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f430a0f2..9b9ec2ae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). -If you're looking for an *Android* version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. +If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. ## Installation @@ -48,15 +48,18 @@ The library implements JWT Verification and Signing using the following algorith ### Create and Sign a Token -You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the Algorithm instance. +You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the `Algorithm` instance. * Example using `HS256` ```java try { + Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") - .sign(Algorithm.HMAC256("secret")); + .sign(algorithm); +} catch (UnsupportedEncodingException exception){ + //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. } @@ -65,11 +68,13 @@ try { * Example using `RS256` ```java -PrivateKey key = //Get the key instance +RSAPublicKey publicKey = //Get the key instance +RSAPrivateKey privateKey = //Get the key instance try { + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); String token = JWT.create() .withIssuer("auth0") - .sign(Algorithm.RSA256(key)); + .sign(algorithm); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. } @@ -80,17 +85,20 @@ If a Claim couldn't be converted to JSON or the Key used in the signing process ### Verify a Token -You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the Algorithm instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. +You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. * Example using `HS256` ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { - JWTVerifier verifier = JWT.require(Algorithm.HMAC256("secret")) + Algorithm algorithm = Algorithm.HMAC256("secret"); + JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); +} catch (UnsupportedEncodingException exception){ + //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims } @@ -100,9 +108,11 @@ try { ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -PublicKey key = //Get the key instance +RSAPublicKey publicKey = //Get the key instance +RSAPrivateKey privateKey = //Get the key instance try { - JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); @@ -126,7 +136,7 @@ When verifying a token the time validation occurs automatically, resulting in a To specify a **leeway window** in which the Token should still be considered valid, use the `acceptLeeway()` method in the `JWTVerifier` builder and pass a positive seconds value. This applies to every item listed above. ```java -JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) +JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) // 1 sec for nbf, iat and exp .build(); ``` @@ -134,7 +144,7 @@ JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) You can also specify a custom value for a given Date claim and override the default one for only that claim. ```java -JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) +JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) //1 sec for nbf and iat .acceptExpiresAt(5) //5 secs for exp .build(); @@ -143,7 +153,7 @@ JWTVerifier verifier = JWT.require(Algorithm.RSA256(key)) If you need to test this behaviour in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a custom `Clock`. e.g.: ```java -BaseVerification verification = (BaseVerification) JWT.require(Algorithm.RSA256(key)) +BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); Clock clock = new CustomClock(); //Must implement Clock interface @@ -211,9 +221,9 @@ When creating a Token with the `JWT.create()` you can specify header Claims by c ```java Map headerClaims = new HashMap(); headerclaims.put("owner", "auth0"); -JWT.create() - .withHeader(headerClaims) - .sign(Algorithm.HMAC256("secret")); +String token = JWT.create() + .withHeader(headerClaims) + .sign(algorithm); ``` > The `alg` and `typ` values will always be included in the Header after the signing process. @@ -295,20 +305,20 @@ Claim claim = jwt.getClaim("isAdmin"); When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. ```java -JWT.create() - .withClaim("name", 123) - .withArrayClaim("array", new Integer[]{1, 2, 3}) - .sign(Algorithm.HMAC256("secret")); +String token = JWT.create() + .withClaim("name", 123) + .withArrayClaim("array", new Integer[]{1, 2, 3}) + .sign(algorithm); ``` You can also verify custom Claims on the `JWT.require()` by calling `withClaim()` and passing both the name and the required value. ```java -JWT.require(Algorithm.HMAC256("secret")) +JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) - .build() - .verify("my.jwt.token"); + .build(); +DecodedJWT jwt = verifier.verify("my.jwt.token"); ``` > Currently supported classes for custom JWT Claim creation and verification are: Boolean, Integer, Double, String, Date and Arrays of type String and Integer. From c4f209439f1921c47c9d0bdac238a8df57f99f9f Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 13 Mar 2017 10:44:07 -0300 Subject: [PATCH 031/355] chore cast and exception handling --- .../com/auth0/jwt/algorithms/Algorithm.java | 26 +++++++++---------- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 8 +++--- .../auth0/jwt/algorithms/RSAAlgorithm.java | 8 +++--- .../jwt/algorithms/ECDSAAlgorithmTest.java | 12 ++++----- .../jwt/algorithms/RSAAlgorithmTest.java | 12 ++++----- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 589210cb..508e10a5 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -4,8 +4,6 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import java.io.UnsupportedEncodingException; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.interfaces.*; /** @@ -27,8 +25,8 @@ public abstract class Algorithm { */ @Deprecated public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { - RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; - RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; return RSA256(publicKey, privateKey); } @@ -42,8 +40,8 @@ public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { */ @Deprecated public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { - RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; - RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; return RSA384(publicKey, privateKey); } @@ -57,8 +55,8 @@ public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { */ @Deprecated public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { - RSAPublicKey publicKey = key instanceof PublicKey ? (RSAPublicKey) key : null; - RSAPrivateKey privateKey = key instanceof PrivateKey ? (RSAPrivateKey) key : null; + RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; return RSA512(publicKey, privateKey); } @@ -177,8 +175,8 @@ public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { */ @Deprecated public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; return ECDSA256(publicKey, privateKey); } @@ -192,8 +190,8 @@ public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { */ @Deprecated public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; return ECDSA384(publicKey, privateKey); } @@ -207,8 +205,8 @@ public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { */ @Deprecated public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof PublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof PrivateKey ? (ECPrivateKey) key : null; + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; return ECDSA512(publicKey, privateKey); } 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 cc47f86b..135387bd 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -44,7 +44,7 @@ ECPrivateKey getPrivateKey() { public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { if (publicKey == null) { - throw new IllegalArgumentException("The given Public Key is null."); + throw new IllegalStateException("The given Public Key is null."); } if (!isDERSignature(signatureBytes)) { signatureBytes = JOSEToDER(signatureBytes); @@ -54,7 +54,7 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } @@ -63,10 +63,10 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { if (privateKey == null) { - throw new IllegalArgumentException("The given Private Key is null."); + throw new IllegalStateException("The given Private Key is null."); } return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 2ab08331..83b3658f 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -42,13 +42,13 @@ RSAPrivateKey getPrivateKey() { public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { if (publicKey == null) { - throw new IllegalArgumentException("The given Public Key is null."); + throw new IllegalStateException("The given Public Key is null."); } boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } @@ -57,10 +57,10 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { if (privateKey == null) { - throw new IllegalArgumentException("The given Private Key is null."); + throw new IllegalStateException("The given Private Key is null."); } return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } 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 b459f6d4..71383744 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -88,7 +88,7 @@ public void shouldFailECDSA256VerificationWithInvalidPublicKey() throws Exceptio 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(IllegalArgumentException.class)); + 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")); @@ -180,7 +180,7 @@ public void shouldFailECDSA384VerificationWithInvalidPublicKey() throws Exceptio 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(IllegalArgumentException.class)); + 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")); @@ -272,7 +272,7 @@ public void shouldFailECDSA512VerificationWithInvalidPublicKey() throws Exceptio 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(IllegalArgumentException.class)); + 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")); @@ -421,7 +421,7 @@ public void shouldDoECDSA256SigningWithBothKeys() throws Exception { 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(IllegalArgumentException.class)); + 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")); @@ -453,7 +453,7 @@ public void shouldDoECDSA384SigningWithBothKeys() throws Exception { 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(IllegalArgumentException.class)); + 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")); @@ -485,7 +485,7 @@ public void shouldDoECDSA512SigningWithBothKeys() throws Exception { 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(IllegalArgumentException.class)); + 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")); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index 15610664..e7f638db 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -61,7 +61,7 @@ public void shouldFailRSA256VerificationWithInvalidPublicKey() throws Exception public void shouldFailRSA256VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); @@ -95,7 +95,7 @@ public void shouldFailRSA384VerificationWithInvalidPublicKey() throws Exception public void shouldFailRSA384VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); @@ -129,7 +129,7 @@ public void shouldFailRSA512VerificationWithInvalidPublicKey() throws Exception public void shouldFailRSA512VerificationWhenUsingPrivateKey() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); @@ -227,7 +227,7 @@ public void shouldDoRSA256SigningWithBothKeys() throws Exception { public void shouldFailOnRSA256SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); @@ -267,7 +267,7 @@ public void shouldDoRSA384SigningWithBothKeys() throws Exception { public void shouldFailOnRSA384SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA384withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); @@ -307,7 +307,7 @@ public void shouldDoRSA512SigningWithBothKeys() throws Exception { public void shouldFailOnRSA512SigningWhenUsingPublicKey() throws Exception { exception.expect(SignatureGenerationException.class); exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA512withRSA"); - exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); From 06311233471445d2b31a3534881ff0bd23b8b598 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 13 Mar 2017 12:39:26 -0300 Subject: [PATCH 032/355] add algorithm key provider interface --- .../com/auth0/jwt/algorithms/Algorithm.java | 70 ++++++++++++++++++- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 50 +++++++++---- .../auth0/jwt/algorithms/RSAAlgorithm.java | 47 +++++++++---- .../auth0/jwt/interfaces/ECKeyProvider.java | 10 +++ .../com/auth0/jwt/interfaces/KeyProvider.java | 27 +++++++ .../auth0/jwt/interfaces/RSAKeyProvider.java | 10 +++ .../auth0/jwt/algorithms/AlgorithmTest.java | 68 ++++++++++++++++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 19 +++-- .../jwt/algorithms/RSAAlgorithmTest.java | 19 +++-- 9 files changed, 274 insertions(+), 46 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java create mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java create mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/RSAKeyProvider.java diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 508e10a5..fc05b924 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -2,6 +2,8 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.RSAKeyProvider; import java.io.UnsupportedEncodingException; import java.security.interfaces.*; @@ -18,9 +20,20 @@ public abstract class Algorithm { /** * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". * - * @param key the key to use in the verify or signing instance. + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. * @return a valid RSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + */ + public static Algorithm RSA256(RSAKeyProvider keyProvider) throws IllegalArgumentException { + return new RSAAlgorithm("RS256", "SHA256withRSA", keyProvider); + } + + /** + * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". + * + * @param key the key to use in the verify or signing instance. + * @return a valid RSA256 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. * @deprecated use {@link #RSA256(RSAPublicKey, RSAPrivateKey)} */ @Deprecated @@ -45,6 +58,17 @@ public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { return RSA384(publicKey, privateKey); } + /** + * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid RSA384 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm RSA384(RSAKeyProvider keyProvider) throws IllegalArgumentException { + return new RSAAlgorithm("RS384", "SHA384withRSA", keyProvider); + } + /** * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". * @@ -96,6 +120,17 @@ public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) return new RSAAlgorithm("RS512", "SHA512withRSA", publicKey, privateKey); } + /** + * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid RSA512 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm RSA512(RSAKeyProvider keyProvider) throws IllegalArgumentException { + return new RSAAlgorithm("RS512", "SHA512withRSA", keyProvider); + } + /** * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * @@ -180,6 +215,17 @@ public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { return ECDSA256(publicKey, privateKey); } + /** + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid ECDSA256 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider); + } + /** * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". * @@ -195,6 +241,17 @@ public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { return ECDSA384(publicKey, privateKey); } + /** + * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid ECDSA384 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm ECDSA384(ECKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, keyProvider); + } + /** * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". * @@ -246,6 +303,17 @@ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, publicKey, privateKey); } + /** + * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid ECDSA512 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider); + } + public static Algorithm none() { return new NoneAlgorithm(); } 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 135387bd..04c95599 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.ECKeyProvider; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -11,45 +12,50 @@ class ECDSAAlgorithm extends Algorithm { - private final ECPublicKey publicKey; - private final ECPrivateKey privateKey; + private final ECKeyProvider keyProvider; private final CryptoHelper crypto; private final int ecNumberSize; //Visible for testing - ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException { super(id, algorithm); - if (publicKey == null && privateKey == null) { + if (keyProvider == null) { + throw new IllegalArgumentException("The Key Provider cannot be null."); + } + if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) { throw new IllegalArgumentException("Both provided Keys cannot be null."); } - this.publicKey = publicKey; - this.privateKey = privateKey; - this.ecNumberSize = ecNumberSize; + this.keyProvider = keyProvider; this.crypto = crypto; + this.ecNumberSize = ecNumberSize; } ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, ecNumberSize, publicKey, privateKey); + this(new CryptoHelper(), id, algorithm, ecNumberSize, providerForKeys(publicKey, privateKey)); + } + + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException { + this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } ECPublicKey getPublicKey() { - return publicKey; + return keyProvider.getPublicKey(); } ECPrivateKey getPrivateKey() { - return privateKey; + return keyProvider.getPrivateKey(); } @Override public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { - if (publicKey == null) { + if (keyProvider.getPublicKey() == 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(), keyProvider.getPublicKey(), contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); @@ -62,15 +68,16 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (privateKey == null) { + if (keyProvider.getPrivateKey() == null) { throw new IllegalStateException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); + return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } + private boolean isDERSignature(byte[] signature) { // DER Structure: http://crypto.stackexchange.com/a/1797 // Should begin with 0x30 and have exactly the expected length @@ -132,4 +139,19 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) { } return bytes[fromIndex + padding] > 0x7f ? padding : padding - 1; } + + //Visible for testing + static ECKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) { + return new ECKeyProvider() { + @Override + public ECPublicKey getPublicKey() { + return publicKey; + } + + @Override + public ECPrivateKey getPrivateKey() { + return privateKey; + } + }; + } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 83b3658f..c9f708d6 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.RSAKeyProvider; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -11,40 +12,45 @@ class RSAAlgorithm extends Algorithm { - private final RSAPublicKey publicKey; - private final RSAPrivateKey privateKey; + private final RSAKeyProvider keyProvider; private final CryptoHelper crypto; //Visible for testing - RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException { super(id, algorithm); - if (publicKey == null && privateKey == null) { + if (keyProvider == null) { + throw new IllegalArgumentException("The Key Provider cannot be null."); + } + if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) { throw new IllegalArgumentException("Both provided Keys cannot be null."); } - this.publicKey = publicKey; - this.privateKey = privateKey; + this.keyProvider = keyProvider; this.crypto = crypto; } RSAAlgorithm(String id, String algorithm, RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, publicKey, privateKey); + this(new CryptoHelper(), id, algorithm, providerForKeys(publicKey, privateKey)); + } + + RSAAlgorithm(String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException { + this(new CryptoHelper(), id, algorithm, keyProvider); } RSAPublicKey getPublicKey() { - return publicKey; + return keyProvider.getPublicKey(); } RSAPrivateKey getPrivateKey() { - return privateKey; + return keyProvider.getPrivateKey(); } @Override public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { try { - if (publicKey == null) { + if (keyProvider.getPublicKey() == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), keyProvider.getPublicKey(), contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } @@ -56,12 +62,27 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (privateKey == null) { + if (keyProvider.getPrivateKey() == null) { throw new IllegalStateException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); + return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } + + //Visible for testing + static RSAKeyProvider providerForKeys(final RSAPublicKey publicKey, final RSAPrivateKey privateKey) { + return new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKey() { + return publicKey; + } + + @Override + public RSAPrivateKey getPrivateKey() { + return privateKey; + } + }; + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java new file mode 100644 index 00000000..ff14f9e8 --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java @@ -0,0 +1,10 @@ +package com.auth0.jwt.interfaces; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * Elliptic Curve (EC) Public/Private Key provider. + */ +public interface ECKeyProvider extends KeyProvider { +} diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java new file mode 100644 index 00000000..10f419fe --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -0,0 +1,27 @@ +package com.auth0.jwt.interfaces; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * Generic Public/Private Key provider. + * + * @param the class that represents the Public Key + * @param the class that represents the Private Key + */ +interface KeyProvider { + + /** + * Getter for the Public Key instance, used to verify the signature. + * + * @return the Public Key instance + */ + U getPublicKey(); + + /** + * Getter for the Private Key instance, used to sign the content. + * + * @return the Private Key instance + */ + R getPrivateKey(); +} diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/RSAKeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/RSAKeyProvider.java new file mode 100644 index 00000000..55376f4d --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/interfaces/RSAKeyProvider.java @@ -0,0 +1,10 @@ +package com.auth0.jwt.interfaces; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * RSA Public/Private Key provider. + */ +public interface RSAKeyProvider extends KeyProvider { +} diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index 0b0c6249..7fa463be 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -1,5 +1,7 @@ package com.auth0.jwt.algorithms; +import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.RSAKeyProvider; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -70,7 +72,8 @@ public void shouldThrowHMAC512InstanceWithNullSecret() throws Exception { public void shouldThrowRSA256InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.RSA256(null); + RSAKey key = null; + Algorithm.RSA256(key); } @Test @@ -80,11 +83,20 @@ public void shouldThrowRSA256InstanceWithNullKeys() throws Exception { Algorithm.RSA256(null, null); } + @Test + public void shouldThrowRSA256InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + RSAKeyProvider provider = null; + Algorithm.RSA256(provider); + } + @Test public void shouldThrowRSA384InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.RSA384(null); + RSAKey key = null; + Algorithm.RSA384(key); } @Test @@ -94,11 +106,20 @@ public void shouldThrowRSA384InstanceWithNullKeys() throws Exception { Algorithm.RSA384(null, null); } + @Test + public void shouldThrowRSA384InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + RSAKeyProvider provider = null; + Algorithm.RSA384(provider); + } + @Test public void shouldThrowRSA512InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.RSA512(null); + RSAKey key = null; + Algorithm.RSA512(key); } @Test @@ -108,11 +129,20 @@ public void shouldThrowRSA512InstanceWithNullKeys() throws Exception { Algorithm.RSA512(null, null); } + @Test + public void shouldThrowRSA512InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + RSAKeyProvider provider = null; + Algorithm.RSA512(provider); + } + @Test public void shouldThrowECDSA256InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.ECDSA256(null); + ECKey key = null; + Algorithm.ECDSA256(key); } @Test @@ -122,11 +152,20 @@ public void shouldThrowECDSA256InstanceWithNullKeys() throws Exception { Algorithm.ECDSA256(null, null); } + @Test + public void shouldThrowECDSA256InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + ECKeyProvider provider = null; + Algorithm.ECDSA256(provider); + } + @Test public void shouldThrowECDSA384InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.ECDSA384(null); + ECKey key = null; + Algorithm.ECDSA384(key); } @Test @@ -136,11 +175,20 @@ public void shouldThrowECDSA384InstanceWithNullKeys() throws Exception { Algorithm.ECDSA384(null, null); } + @Test + public void shouldThrowECDSA384InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + ECKeyProvider provider = null; + Algorithm.ECDSA384(provider); + } + @Test public void shouldThrowECDSA512InstanceWithNullKey() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("Both provided Keys cannot be null."); - Algorithm.ECDSA512(null); + ECKey key = null; + Algorithm.ECDSA512(key); } @Test @@ -150,6 +198,14 @@ public void shouldThrowECDSA512InstanceWithNullKeys() throws Exception { Algorithm.ECDSA512(null, null); } + @Test + public void shouldThrowECDSA512InstanceWithNullKeyProvider() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Key Provider cannot be null."); + ECKeyProvider provider = null; + Algorithm.ECDSA512(provider); + } + @Test public void shouldCreateHMAC256AlgorithmWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); 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 71383744..40f484b2 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.ECKeyProvider; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -351,7 +352,8 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider 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"; AlgorithmUtils.verify(algorithm, jwt); } @@ -368,7 +370,8 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider 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"; AlgorithmUtils.verify(algorithm, jwt); } @@ -385,7 +388,8 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider 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"; AlgorithmUtils.verify(algorithm, jwt); } @@ -504,7 +508,8 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -520,7 +525,8 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -536,7 +542,8 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, publicKey, privateKey); + ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index e7f638db..951d83f0 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.RSAKeyProvider; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -148,7 +149,8 @@ public void shouldThrowWhenMacAlgorithmDoesNotExists() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -165,7 +167,8 @@ public void shouldThrowWhenThePublicKeyIsInvalid() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -182,7 +185,8 @@ public void shouldThrowWhenTheSignatureIsNotPrepared() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; AlgorithmUtils.verify(algorithm, jwt); } @@ -326,7 +330,8 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } @@ -342,7 +347,8 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } @@ -358,7 +364,8 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); - Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", publicKey, privateKey); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } } \ No newline at end of file From f27584ec49c9d93b39c6f71a95429d0b2a04f065 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 13 Mar 2017 12:48:50 -0300 Subject: [PATCH 033/355] reorder methods in the Algorithm class --- .../com/auth0/jwt/algorithms/Algorithm.java | 167 +++++++++--------- 1 file changed, 84 insertions(+), 83 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index fc05b924..ec4ab405 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -28,6 +28,18 @@ public static Algorithm RSA256(RSAKeyProvider keyProvider) throws IllegalArgumen return new RSAAlgorithm("RS256", "SHA256withRSA", keyProvider); } + /** + * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid RSA256 Algorithm. + * @throws IllegalArgumentException if both provided Keys are null. + */ + public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + return new RSAAlgorithm("RS256", "SHA256withRSA", publicKey, privateKey); + } + /** * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". * @@ -46,66 +58,50 @@ public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { /** * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". * - * @param key the key to use in the verify or signing instance. + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. * @return a valid RSA384 Algorithm. - * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #RSA384(RSAPublicKey, RSAPrivateKey)} + * @throws IllegalArgumentException if the Key Provider is null. */ - @Deprecated - public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { - RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; - RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; - return RSA384(publicKey, privateKey); + public static Algorithm RSA384(RSAKeyProvider keyProvider) throws IllegalArgumentException { + return new RSAAlgorithm("RS384", "SHA384withRSA", keyProvider); } /** * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". * - * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. * @return a valid RSA384 Algorithm. - * @throws IllegalArgumentException if the Key Provider is null. + * @throws IllegalArgumentException if both provided Keys are null. */ - public static Algorithm RSA384(RSAKeyProvider keyProvider) throws IllegalArgumentException { - return new RSAAlgorithm("RS384", "SHA384withRSA", keyProvider); + public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { + return new RSAAlgorithm("RS384", "SHA384withRSA", publicKey, privateKey); } /** - * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". + * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". * * @param key the key to use in the verify or signing instance. - * @return a valid RSA512 Algorithm. + * @return a valid RSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #RSA512(RSAPublicKey, RSAPrivateKey)} + * @deprecated use {@link #RSA384(RSAPublicKey, RSAPrivateKey)} */ @Deprecated - public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { + public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; - return RSA512(publicKey, privateKey); - } - - /** - * Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256". - * - * @param publicKey the key to use in the verify instance. - * @param privateKey the key to use in the signing instance. - * @return a valid RSA256 Algorithm. - * @throws IllegalArgumentException if both provided Keys are null. - */ - public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - return new RSAAlgorithm("RS256", "SHA256withRSA", publicKey, privateKey); + return RSA384(publicKey, privateKey); } /** - * Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384". + * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". * - * @param publicKey the key to use in the verify instance. - * @param privateKey the key to use in the signing instance. - * @return a valid RSA384 Algorithm. - * @throws IllegalArgumentException if both provided Keys are null. + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid RSA512 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. */ - public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - return new RSAAlgorithm("RS384", "SHA384withRSA", publicKey, privateKey); + public static Algorithm RSA512(RSAKeyProvider keyProvider) throws IllegalArgumentException { + return new RSAAlgorithm("RS512", "SHA512withRSA", keyProvider); } /** @@ -123,12 +119,16 @@ public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) /** * Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512". * - * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @param key the key to use in the verify or signing instance. * @return a valid RSA512 Algorithm. - * @throws IllegalArgumentException if the Key Provider is null. + * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #RSA512(RSAPublicKey, RSAPrivateKey)} */ - public static Algorithm RSA512(RSAKeyProvider keyProvider) throws IllegalArgumentException { - return new RSAAlgorithm("RS512", "SHA512withRSA", keyProvider); + @Deprecated + public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { + RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null; + RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null; + return RSA512(publicKey, privateKey); } /** @@ -203,42 +203,39 @@ public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". * - * @param key the key to use in the verify or signing instance. + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. * @return a valid ECDSA256 Algorithm. - * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} + * @throws IllegalArgumentException if the Key Provider is null. */ - @Deprecated - public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; - return ECDSA256(publicKey, privateKey); + public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider); } /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". * - * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. * @return a valid ECDSA256 Algorithm. - * @throws IllegalArgumentException if the Key Provider is null. + * @throws IllegalArgumentException if the provided Key is null. */ - public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider); + public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, publicKey, privateKey); } /** - * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". * * @param key the key to use in the verify or signing instance. - * @return a valid ECDSA384 Algorithm. + * @return a valid ECDSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} + * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} */ @Deprecated - public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { + public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; - return ECDSA384(publicKey, privateKey); + return ECDSA256(publicKey, privateKey); } /** @@ -253,42 +250,41 @@ public static Algorithm ECDSA384(ECKeyProvider keyProvider) throws IllegalArgume } /** - * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". + * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". * - * @param key the key to use in the verify or signing instance. - * @return a valid ECDSA512 Algorithm. + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} */ - @Deprecated - public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; - return ECDSA512(publicKey, privateKey); + public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, publicKey, privateKey); } /** - * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". + * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". * - * @param publicKey the key to use in the verify instance. - * @param privateKey the key to use in the signing instance. - * @return a valid ECDSA256 Algorithm. + * @param key the key to use in the verify or signing instance. + * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} */ - public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, publicKey, privateKey); + @Deprecated + public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; + return ECDSA384(publicKey, privateKey); } /** - * Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384". + * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". * - * @param publicKey the key to use in the verify instance. - * @param privateKey the key to use in the signing instance. - * @return a valid ECDSA384 Algorithm. - * @throws IllegalArgumentException if the provided Key is null. + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid ECDSA512 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. */ - public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, publicKey, privateKey); + public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider); } /** @@ -306,14 +302,19 @@ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) /** * Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512". * - * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @param key the key to use in the verify or signing instance. * @return a valid ECDSA512 Algorithm. - * @throws IllegalArgumentException if the Key Provider is null. + * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} */ - public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider); + @Deprecated + public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; + return ECDSA512(publicKey, privateKey); } + public static Algorithm none() { return new NoneAlgorithm(); } From 18c17109cd8a95f69a57c86cc04c0d0da68036e2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 14 Mar 2017 11:45:36 -0300 Subject: [PATCH 034/355] remove constructor with 2 keys. Keep keyProvider signature --- .../com/auth0/jwt/algorithms/Algorithm.java | 24 +++++++++---------- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 4 ---- .../auth0/jwt/algorithms/RSAAlgorithm.java | 4 ---- .../jwt/algorithms/ECDSAAlgorithmTest.java | 3 ++- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index ec4ab405..eb484478 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -37,7 +37,7 @@ public static Algorithm RSA256(RSAKeyProvider keyProvider) throws IllegalArgumen * @throws IllegalArgumentException if both provided Keys are null. */ public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - return new RSAAlgorithm("RS256", "SHA256withRSA", publicKey, privateKey); + return RSA256(RSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -46,7 +46,7 @@ public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid RSA256 Algorithm. * @throws IllegalArgumentException if the Key Provider is null. - * @deprecated use {@link #RSA256(RSAPublicKey, RSAPrivateKey)} + * @deprecated use {@link #RSA256(RSAPublicKey, RSAPrivateKey)} or {@link #RSA256(RSAKeyProvider)} */ @Deprecated public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException { @@ -75,7 +75,7 @@ public static Algorithm RSA384(RSAKeyProvider keyProvider) throws IllegalArgumen * @throws IllegalArgumentException if both provided Keys are null. */ public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - return new RSAAlgorithm("RS384", "SHA384withRSA", publicKey, privateKey); + return RSA384(RSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -84,7 +84,7 @@ public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid RSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #RSA384(RSAPublicKey, RSAPrivateKey)} + * @deprecated use {@link #RSA384(RSAPublicKey, RSAPrivateKey)} or {@link #RSA384(RSAKeyProvider)} */ @Deprecated public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException { @@ -113,7 +113,7 @@ public static Algorithm RSA512(RSAKeyProvider keyProvider) throws IllegalArgumen * @throws IllegalArgumentException if both provided Keys are null. */ public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - return new RSAAlgorithm("RS512", "SHA512withRSA", publicKey, privateKey); + return RSA512(RSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -122,7 +122,7 @@ public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid RSA512 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #RSA512(RSAPublicKey, RSAPrivateKey)} + * @deprecated use {@link #RSA512(RSAPublicKey, RSAPrivateKey)} or {@link #RSA512(RSAKeyProvider)} */ @Deprecated public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { @@ -220,7 +220,7 @@ public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgume * @throws IllegalArgumentException if the provided Key is null. */ public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, publicKey, privateKey); + return ECDSA256(ECDSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -229,7 +229,7 @@ public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} + * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECKeyProvider)} */ @Deprecated public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { @@ -258,7 +258,7 @@ public static Algorithm ECDSA384(ECKeyProvider keyProvider) throws IllegalArgume * @throws IllegalArgumentException if the provided Key is null. */ public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, publicKey, privateKey); + return ECDSA384(ECDSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -267,7 +267,7 @@ public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} + * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} or {@link #ECDSA384(ECKeyProvider)} */ @Deprecated public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { @@ -296,7 +296,7 @@ public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgume * @throws IllegalArgumentException if the provided Key is null. */ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, publicKey, privateKey); + return ECDSA512(ECDSAAlgorithm.providerForKeys(publicKey, privateKey)); } /** @@ -305,7 +305,7 @@ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA512 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} + * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} or {@link #ECDSA512(ECKeyProvider)} */ @Deprecated public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { 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 04c95599..2b43c7e6 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -30,10 +30,6 @@ class ECDSAAlgorithm extends Algorithm { this.ecNumberSize = ecNumberSize; } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, ecNumberSize, providerForKeys(publicKey, privateKey)); - } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index c9f708d6..a5d7d3f3 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -28,10 +28,6 @@ class RSAAlgorithm extends Algorithm { this.crypto = crypto; } - RSAAlgorithm(String id, String algorithm, RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException { - this(new CryptoHelper(), id, algorithm, providerForKeys(publicKey, privateKey)); - } - RSAAlgorithm(String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, keyProvider); } 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 40f484b2..b169b554 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -336,7 +336,8 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = mock(ECPrivateKey.class); - Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, publicKey, privateKey); + ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, provider); AlgorithmUtils.verify(algorithm, jwt); } From ecb41539d79653c7e9a027b02e5e68c2f55a6252 Mon Sep 17 00:00:00 2001 From: Lorenzo Spinelli Date: Thu, 16 Mar 2017 09:40:44 +0100 Subject: [PATCH 035/355] added date validation dedicated exception --- .../main/java/com/auth0/jwt/JWTVerifier.java | 915 ++++++++++-------- .../jwt/exceptions/InvalidDateException.java | 9 + 2 files changed, 499 insertions(+), 425 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 0ee93f76..bb9a01a1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,8 +1,19 @@ package com.auth0.jwt; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; + import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; +import com.auth0.jwt.exceptions.InvalidDateException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.impl.PublicClaims; @@ -10,434 +21,488 @@ import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; -import org.apache.commons.codec.binary.Base64; - -import java.nio.charset.StandardCharsets; -import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token + * has not only a proper JWT format, but also it's signature matches. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier { - private final Algorithm algorithm; - final Map claims; - private final Clock clock; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { - this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; - } - - /** - * Initialize a JWTVerifier instance using the given Algorithm. - * - * @param algorithm the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. - * @throws IllegalArgumentException if the provided algorithm is null. - */ - static Verification init(Algorithm algorithm) throws IllegalArgumentException { - return new BaseVerification(algorithm); - } - - /** - * The Verification class holds the Claims required by a JWT to be valid. - */ - public static class BaseVerification implements Verification { - private final Algorithm algorithm; - private final Map claims; - private long defaultLeeway; - - BaseVerification(Algorithm algorithm) throws IllegalArgumentException { - if (algorithm == null) { - throw new IllegalArgumentException("The Algorithm cannot be null."); - } - - this.algorithm = algorithm; - this.claims = new HashMap<>(); - this.defaultLeeway = 0; - } - - /** - * Require a specific Issuer ("iss") claim. - * - * @param issuer the required Issuer value - * @return this same Verification instance. - */ - @Override - public Verification withIssuer(String issuer) { - requireClaim(PublicClaims.ISSUER, issuer); - return this; - } - - /** - * Require a specific Subject ("sub") claim. - * - * @param subject the required Subject value - * @return this same Verification instance. - */ - @Override - public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); - return this; - } - - /** - * Require a specific Audience ("aud") claim. - * - * @param audience the required Audience value - * @return this same Verification instance. - */ - @Override - public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); - return this; - } - - /** - * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * Setting a specific leeway value on a given Claim will override this value for that Claim. - * - * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptLeeway(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - this.defaultLeeway = leeway; - return this; - } - - /** - * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. - * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Expires At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. - * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Not Before Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. - * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Issued At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); - return this; - } - - /** - * Require a specific JWT Id ("jti") claim. - * - * @param jwtId the required Id value - * @return this same Verification instance. - */ - @Override - public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Integer value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Double value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, String value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Date value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. - * - * @return a new JWTVerifier instance. - */ - @Override - public JWTVerifier build() { - return this.build(new ClockImpl()); - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. - * ONLY FOR TEST PURPOSES. - * - * @param clock the instance that will handle the current time. - * @return a new JWTVerifier instance with a custom Clock. - */ - public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); - } - - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } - } - - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } - } - - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - } - - private void requireClaim(String name, Object value) { - if (value == null) { - claims.remove(name); - return; - } - claims.put(name, value); - } - } - - - /** - * Perform the verification against the given Token, using any previous configured options. - * - * @param token to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. - */ - public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWTDecoder.decode(token); - verifyAlgorithm(jwt, algorithm); - verifySignature(TokenUtils.splitToken(token)); - verifyClaims(jwt, claims); - return jwt; - } - - private void verifySignature(String[] parts) throws SignatureVerificationException { - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = Base64.decodeBase64(parts[2]); - algorithm.verify(content, signature); - } - - private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { - if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); - } - } - - private void verifyClaims(DecodedJWT jwt, Map claims) { - for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - //noinspection unchecked - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; - } - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Date) { - isValid = value.equals(claim.asDate()); - } else if (value instanceof Object[]) { - List claimArr = Arrays.asList(claim.as(Object[].class)); - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); - today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); //truncate millis - boolean isValid; - String errMessage; - if (shouldBeFuture) { - today.setTime(today.getTime() - leeway * 1000); - isValid = date == null || !today.after(date); - errMessage = String.format("The Token has expired on %s.", date); - } else { - today.setTime(today.getTime() + leeway * 1000); - isValid = date == null || !today.before(date); - errMessage = String.format("The Token can't be used before %s.", date); - } - if (!isValid) { - throw new InvalidClaimException(errMessage); - } - } - - private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } + /** + * The Verification class holds the Claims required by a JWT to be valid. + */ + public static class BaseVerification implements Verification { + private final Algorithm algorithm; + private final Map claims; + private long defaultLeeway; + + BaseVerification(Algorithm algorithm) throws IllegalArgumentException { + if (algorithm == null) { + throw new IllegalArgumentException("The Algorithm cannot be null."); + } + + this.algorithm = algorithm; + this.claims = new HashMap<>(); + this.defaultLeeway = 0; + } + + /** + * Set a specific leeway window in seconds in which the Expires At + * ("exp") Claim will still be valid. Expiration Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Expires At Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.EXPIRES_AT, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Issued At + * ("iat") Claim will still be valid. Issued At Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Issued At Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.ISSUED_AT, leeway); + return this; + } + + /** + * Define the default window in seconds in which the Not Before, Issued + * At and Expires At Claims will still be valid. Setting a specific + * leeway value on a given Claim will override this value for that + * Claim. + * + * @param leeway + * the window in seconds in which the Not Before, Issued At + * and Expires At Claims will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptLeeway(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + this.defaultLeeway = leeway; + return this; + } + + /** + * Set a specific leeway window in seconds in which the Not Before + * ("nbf") Claim will still be valid. Not Before Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Not Before Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.NOT_BEFORE, leeway); + return this; + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the + * configuration already provided. + * + * @return a new JWTVerifier instance. + */ + @Override + public JWTVerifier build() { + return this.build(new ClockImpl()); + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the + * configuration already provided. ONLY FOR TEST PURPOSES. + * + * @param clock + * the instance that will handle the current time. + * @return a new JWTVerifier instance with a custom Clock. + */ + public JWTVerifier build(Clock clock) { + addLeewayToDateClaims(); + return new JWTVerifier(algorithm, claims, clock); + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name + * the Claim's name. + * @param items + * the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name + * the Claim's name. + * @param items + * the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Audience ("aud") claim. + * + * @param audience + * the required Audience value + * @return this same Verification instance. + */ + @Override + public Verification withAudience(String... audience) { + requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Issuer ("iss") claim. + * + * @param issuer + * the required Issuer value + * @return this same Verification instance. + */ + @Override + public Verification withIssuer(String issuer) { + requireClaim(PublicClaims.ISSUER, issuer); + return this; + } + + /** + * Require a specific JWT Id ("jti") claim. + * + * @param jwtId + * the required Id value + * @return this same Verification instance. + */ + @Override + public Verification withJWTId(String jwtId) { + requireClaim(PublicClaims.JWT_ID, jwtId); + return this; + } + + /** + * Require a specific Subject ("sub") claim. + * + * @param subject + * the required Subject value + * @return this same Verification instance. + */ + @Override + public Verification withSubject(String subject) { + requireClaim(PublicClaims.SUBJECT, subject); + return this; + } + + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void requireClaim(String name, Object value) { + if (value == null) { + claims.remove(name); + return; + } + claims.put(name, value); + } + } + + /** + * Initialize a JWTVerifier instance using the given Algorithm. + * + * @param algorithm + * the Algorithm to use on the JWT verification. + * @return a JWTVerifier.Verification instance to configure. + * @throws IllegalArgumentException + * if the provided algorithm is null. + */ + static Verification init(Algorithm algorithm) throws IllegalArgumentException { + return new BaseVerification(algorithm); + } + + private final Algorithm algorithm; + + final Map claims; + + private final Clock clock; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + this.algorithm = algorithm; + this.claims = Collections.unmodifiableMap(claims); + this.clock = clock; + } + + /** + * Perform the verification against the given Token, using any previous + * configured options. + * + * @param token + * to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException + * if any of the required contents inside the JWT is invalid. + */ + public DecodedJWT verify(String token) throws JWTVerificationException { + DecodedJWT jwt = JWTDecoder.decode(token); + verifyAlgorithm(jwt, algorithm); + verifySignature(TokenUtils.splitToken(token)); + verifyClaims(jwt, claims); + return jwt; + } + + private void assertValidAudienceClaim(List audience, List value) { + if (audience == null || !audience.containsAll(value)) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + } + } + + private void assertValidClaim(Claim claim, String claimName, Object value) { + boolean isValid = false; + if (value instanceof String) { + isValid = value.equals(claim.asString()); + } else if (value instanceof Integer) { + isValid = value.equals(claim.asInt()); + } else if (value instanceof Boolean) { + isValid = value.equals(claim.asBoolean()); + } else if (value instanceof Double) { + isValid = value.equals(claim.asDouble()); + } else if (value instanceof Date) { + isValid = value.equals(claim.asDate()); + } else if (value instanceof Object[]) { + List claimArr = Arrays.asList(claim.as(Object[].class)); + List valueArr = Arrays.asList((Object[]) value); + isValid = claimArr.containsAll(valueArr); + } + + if (!isValid) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { + Date today = clock.getToday(); + today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate + // millis + boolean isValid; + String errMessage; + if (shouldBeFuture) { + today.setTime(today.getTime() - leeway * 1000); + isValid = date == null || !today.after(date); + errMessage = String.format("The Token has expired on %s.", date); + } else { + today.setTime(today.getTime() + leeway * 1000); + isValid = date == null || !today.before(date); + errMessage = String.format("The Token can't be used before %s.", date); + } + if (!isValid) { + throw new InvalidDateException(errMessage); + } + } + + private void assertValidStringClaim(String claimName, String value, String expectedValue) { + if (!expectedValue.equals(value)) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { + if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { + throw new AlgorithmMismatchException( + "The provided Algorithm doesn't match the one defined in the JWT's Header."); + } + } + + private void verifyClaims(DecodedJWT jwt, Map claims) { + for (Map.Entry entry : claims.entrySet()) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + // noinspection unchecked + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + } + + private void verifySignature(String[] parts) throws SignatureVerificationException { + byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); + byte[] signature = Base64.decodeBase64(parts[2]); + algorithm.verify(content, signature); + } } diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java new file mode 100644 index 00000000..eefb0d01 --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java @@ -0,0 +1,9 @@ +package com.auth0.jwt.exceptions; + +public class InvalidDateException extends InvalidClaimException { + private static final long serialVersionUID = -7701609746394348413L; + + public InvalidDateException(String message) { + super(message); + } +} From a00a8f7e2fc7340ef4d8cf681fad15ffb5661b92 Mon Sep 17 00:00:00 2001 From: Lorenzo Spinelli Date: Thu, 16 Mar 2017 16:43:51 +0100 Subject: [PATCH 036/355] restored original class elements order --- .../main/java/com/auth0/jwt/JWTVerifier.java | 914 ++++++++---------- 1 file changed, 425 insertions(+), 489 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index bb9a01a1..59f7ecaf 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,15 +1,5 @@ package com.auth0.jwt; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.codec.binary.Base64; - import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; @@ -21,488 +11,434 @@ import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; +import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token - * has not only a proper JWT format, but also it's signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier { - /** - * The Verification class holds the Claims required by a JWT to be valid. - */ - public static class BaseVerification implements Verification { - private final Algorithm algorithm; - private final Map claims; - private long defaultLeeway; - - BaseVerification(Algorithm algorithm) throws IllegalArgumentException { - if (algorithm == null) { - throw new IllegalArgumentException("The Algorithm cannot be null."); - } - - this.algorithm = algorithm; - this.claims = new HashMap<>(); - this.defaultLeeway = 0; - } - - /** - * Set a specific leeway window in seconds in which the Expires At - * ("exp") Claim will still be valid. Expiration Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Expires At Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Issued At - * ("iat") Claim will still be valid. Issued At Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Issued At Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); - return this; - } - - /** - * Define the default window in seconds in which the Not Before, Issued - * At and Expires At Claims will still be valid. Setting a specific - * leeway value on a given Claim will override this value for that - * Claim. - * - * @param leeway - * the window in seconds in which the Not Before, Issued At - * and Expires At Claims will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptLeeway(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - this.defaultLeeway = leeway; - return this; - } - - /** - * Set a specific leeway window in seconds in which the Not Before - * ("nbf") Claim will still be valid. Not Before Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Not Before Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); - return this; - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the - * configuration already provided. - * - * @return a new JWTVerifier instance. - */ - @Override - public JWTVerifier build() { - return this.build(new ClockImpl()); - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the - * configuration already provided. ONLY FOR TEST PURPOSES. - * - * @param clock - * the instance that will handle the current time. - * @return a new JWTVerifier instance with a custom Clock. - */ - public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name - * the Claim's name. - * @param items - * the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name - * the Claim's name. - * @param items - * the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Require a specific Audience ("aud") claim. - * - * @param audience - * the required Audience value - * @return this same Verification instance. - */ - @Override - public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Date value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Double value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Integer value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, String value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Issuer ("iss") claim. - * - * @param issuer - * the required Issuer value - * @return this same Verification instance. - */ - @Override - public Verification withIssuer(String issuer) { - requireClaim(PublicClaims.ISSUER, issuer); - return this; - } - - /** - * Require a specific JWT Id ("jti") claim. - * - * @param jwtId - * the required Id value - * @return this same Verification instance. - */ - @Override - public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); - return this; - } - - /** - * Require a specific Subject ("sub") claim. - * - * @param subject - * the required Subject value - * @return this same Verification instance. - */ - @Override - public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); - return this; - } - - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - } - - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } - } - - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } - } - - private void requireClaim(String name, Object value) { - if (value == null) { - claims.remove(name); - return; - } - claims.put(name, value); - } - } - - /** - * Initialize a JWTVerifier instance using the given Algorithm. - * - * @param algorithm - * the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. - * @throws IllegalArgumentException - * if the provided algorithm is null. - */ - static Verification init(Algorithm algorithm) throws IllegalArgumentException { - return new BaseVerification(algorithm); - } - - private final Algorithm algorithm; - - final Map claims; - - private final Clock clock; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { - this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; - } - - /** - * Perform the verification against the given Token, using any previous - * configured options. - * - * @param token - * to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException - * if any of the required contents inside the JWT is invalid. - */ - public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWTDecoder.decode(token); - verifyAlgorithm(jwt, algorithm); - verifySignature(TokenUtils.splitToken(token)); - verifyClaims(jwt, claims); - return jwt; - } - - private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Date) { - isValid = value.equals(claim.asDate()); - } else if (value instanceof Object[]) { - List claimArr = Arrays.asList(claim.as(Object[].class)); - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); - today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate - // millis - boolean isValid; - String errMessage; - if (shouldBeFuture) { - today.setTime(today.getTime() - leeway * 1000); - isValid = date == null || !today.after(date); - errMessage = String.format("The Token has expired on %s.", date); - } else { - today.setTime(today.getTime() + leeway * 1000); - isValid = date == null || !today.before(date); - errMessage = String.format("The Token can't be used before %s.", date); - } - if (!isValid) { - throw new InvalidDateException(errMessage); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { - if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException( - "The provided Algorithm doesn't match the one defined in the JWT's Header."); - } - } - - private void verifyClaims(DecodedJWT jwt, Map claims) { - for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - // noinspection unchecked - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; - } - } - } - - private void verifySignature(String[] parts) throws SignatureVerificationException { - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = Base64.decodeBase64(parts[2]); - algorithm.verify(content, signature); - } + private final Algorithm algorithm; + final Map claims; + private final Clock clock; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + this.algorithm = algorithm; + this.claims = Collections.unmodifiableMap(claims); + this.clock = clock; + } + + /** + * Initialize a JWTVerifier instance using the given Algorithm. + * + * @param algorithm the Algorithm to use on the JWT verification. + * @return a JWTVerifier.Verification instance to configure. + * @throws IllegalArgumentException if the provided algorithm is null. + */ + static Verification init(Algorithm algorithm) throws IllegalArgumentException { + return new BaseVerification(algorithm); + } + + /** + * The Verification class holds the Claims required by a JWT to be valid. + */ + public static class BaseVerification implements Verification { + private final Algorithm algorithm; + private final Map claims; + private long defaultLeeway; + + BaseVerification(Algorithm algorithm) throws IllegalArgumentException { + if (algorithm == null) { + throw new IllegalArgumentException("The Algorithm cannot be null."); + } + + this.algorithm = algorithm; + this.claims = new HashMap<>(); + this.defaultLeeway = 0; + } + + /** + * Require a specific Issuer ("iss") claim. + * + * @param issuer the required Issuer value + * @return this same Verification instance. + */ + @Override + public Verification withIssuer(String issuer) { + requireClaim(PublicClaims.ISSUER, issuer); + return this; + } + + /** + * Require a specific Subject ("sub") claim. + * + * @param subject the required Subject value + * @return this same Verification instance. + */ + @Override + public Verification withSubject(String subject) { + requireClaim(PublicClaims.SUBJECT, subject); + return this; + } + + /** + * Require a specific Audience ("aud") claim. + * + * @param audience the required Audience value + * @return this same Verification instance. + */ + @Override + public Verification withAudience(String... audience) { + requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); + return this; + } + + /** + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * Setting a specific leeway value on a given Claim will override this value for that Claim. + * + * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptLeeway(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + this.defaultLeeway = leeway; + return this; + } + + /** + * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. + * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Expires At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.EXPIRES_AT, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. + * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Not Before Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.NOT_BEFORE, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. + * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Issued At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.ISSUED_AT, leeway); + return this; + } + + /** + * Require a specific JWT Id ("jti") claim. + * + * @param jwtId the required Id value + * @return this same Verification instance. + */ + @Override + public Verification withJWTId(String jwtId) { + requireClaim(PublicClaims.JWT_ID, jwtId); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * + * @return a new JWTVerifier instance. + */ + @Override + public JWTVerifier build() { + return this.build(new ClockImpl()); + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * ONLY FOR TEST PURPOSES. + * + * @param clock the instance that will handle the current time. + * @return a new JWTVerifier instance with a custom Clock. + */ + public JWTVerifier build(Clock clock) { + addLeewayToDateClaims(); + return new JWTVerifier(algorithm, claims, clock); + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } + + private void requireClaim(String name, Object value) { + if (value == null) { + claims.remove(name); + return; + } + claims.put(name, value); + } + } + + + /** + * Perform the verification against the given Token, using any previous configured options. + * + * @param token to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. + */ + public DecodedJWT verify(String token) throws JWTVerificationException { + DecodedJWT jwt = JWTDecoder.decode(token); + verifyAlgorithm(jwt, algorithm); + verifySignature(TokenUtils.splitToken(token)); + verifyClaims(jwt, claims); + return jwt; + } + + private void verifySignature(String[] parts) throws SignatureVerificationException { + byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); + byte[] signature = Base64.decodeBase64(parts[2]); + algorithm.verify(content, signature); + } + + private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { + if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { + throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); + } + } + + private void verifyClaims(DecodedJWT jwt, Map claims) { + for (Map.Entry entry : claims.entrySet()) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + //noinspection unchecked + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + } + + private void assertValidClaim(Claim claim, String claimName, Object value) { + boolean isValid = false; + if (value instanceof String) { + isValid = value.equals(claim.asString()); + } else if (value instanceof Integer) { + isValid = value.equals(claim.asInt()); + } else if (value instanceof Boolean) { + isValid = value.equals(claim.asBoolean()); + } else if (value instanceof Double) { + isValid = value.equals(claim.asDouble()); + } else if (value instanceof Date) { + isValid = value.equals(claim.asDate()); + } else if (value instanceof Object[]) { + List claimArr = Arrays.asList(claim.as(Object[].class)); + List valueArr = Arrays.asList((Object[]) value); + isValid = claimArr.containsAll(valueArr); + } + + if (!isValid) { + throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidStringClaim(String claimName, String value, String expectedValue) { + if (!expectedValue.equals(value)) { + throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { + Date today = clock.getToday(); + today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); //truncate millis + boolean isValid; + String errMessage; + if (shouldBeFuture) { + today.setTime(today.getTime() - leeway * 1000); + isValid = date == null || !today.after(date); + errMessage = String.format("The Token has expired on %s.", date); + } else { + today.setTime(today.getTime() + leeway * 1000); + isValid = date == null || !today.before(date); + errMessage = String.format("The Token can't be used before %s.", date); + } + if (!isValid) { + throw new InvalidDateException(errMessage); + } + } + + private void assertValidAudienceClaim(List audience, List value) { + if (audience == null || !audience.containsAll(value)) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + } + } } From b4cd84c73e8e702ab23f7edd34524b430d5eb504 Mon Sep 17 00:00:00 2001 From: Lorenzo Spinelli Date: Thu, 16 Mar 2017 17:44:47 +0100 Subject: [PATCH 037/355] changed exception for token expiration in TokenExpirationException --- .../main/java/com/auth0/jwt/JWTVerifier.java | 921 ++++++++++-------- .../jwt/exceptions/InvalidDateException.java | 9 - .../jwt/exceptions/TokenExpiredException.java | 10 + .../java/com/auth0/jwt/JWTVerifierTest.java | 3 +- 4 files changed, 507 insertions(+), 436 deletions(-) delete mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 59f7ecaf..95996763 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,444 +1,513 @@ package com.auth0.jwt; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; + import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; -import com.auth0.jwt.exceptions.InvalidDateException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; -import org.apache.commons.codec.binary.Base64; - -import java.nio.charset.StandardCharsets; -import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token + * has not only a proper JWT format, but also it's signature matches. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier { - private final Algorithm algorithm; - final Map claims; - private final Clock clock; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { - this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; - } - - /** - * Initialize a JWTVerifier instance using the given Algorithm. - * - * @param algorithm the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. - * @throws IllegalArgumentException if the provided algorithm is null. - */ - static Verification init(Algorithm algorithm) throws IllegalArgumentException { - return new BaseVerification(algorithm); - } - - /** - * The Verification class holds the Claims required by a JWT to be valid. - */ - public static class BaseVerification implements Verification { - private final Algorithm algorithm; - private final Map claims; - private long defaultLeeway; - - BaseVerification(Algorithm algorithm) throws IllegalArgumentException { - if (algorithm == null) { - throw new IllegalArgumentException("The Algorithm cannot be null."); - } - - this.algorithm = algorithm; - this.claims = new HashMap<>(); - this.defaultLeeway = 0; - } - - /** - * Require a specific Issuer ("iss") claim. - * - * @param issuer the required Issuer value - * @return this same Verification instance. - */ - @Override - public Verification withIssuer(String issuer) { - requireClaim(PublicClaims.ISSUER, issuer); - return this; - } - - /** - * Require a specific Subject ("sub") claim. - * - * @param subject the required Subject value - * @return this same Verification instance. - */ - @Override - public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); - return this; - } - - /** - * Require a specific Audience ("aud") claim. - * - * @param audience the required Audience value - * @return this same Verification instance. - */ - @Override - public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); - return this; - } - - /** - * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * Setting a specific leeway value on a given Claim will override this value for that Claim. - * - * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptLeeway(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - this.defaultLeeway = leeway; - return this; - } - - /** - * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. - * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Expires At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. - * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Not Before Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. - * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Issued At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ - @Override - public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); - return this; - } - - /** - * Require a specific JWT Id ("jti") claim. - * - * @param jwtId the required Id value - * @return this same Verification instance. - */ - @Override - public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Integer value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Double value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, String value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withClaim(String name, Date value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ - @Override - public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. - * - * @return a new JWTVerifier instance. - */ - @Override - public JWTVerifier build() { - return this.build(new ClockImpl()); - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. - * ONLY FOR TEST PURPOSES. - * - * @param clock the instance that will handle the current time. - * @return a new JWTVerifier instance with a custom Clock. - */ - public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); - } - - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } - } - - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } - } - - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - } - - private void requireClaim(String name, Object value) { - if (value == null) { - claims.remove(name); - return; - } - claims.put(name, value); - } - } - - - /** - * Perform the verification against the given Token, using any previous configured options. - * - * @param token to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. - */ - public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWTDecoder.decode(token); - verifyAlgorithm(jwt, algorithm); - verifySignature(TokenUtils.splitToken(token)); - verifyClaims(jwt, claims); - return jwt; - } - - private void verifySignature(String[] parts) throws SignatureVerificationException { - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = Base64.decodeBase64(parts[2]); - algorithm.verify(content, signature); - } - - private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { - if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); - } - } - - private void verifyClaims(DecodedJWT jwt, Map claims) { - for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - //noinspection unchecked - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; - } - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Date) { - isValid = value.equals(claim.asDate()); - } else if (value instanceof Object[]) { - List claimArr = Arrays.asList(claim.as(Object[].class)); - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); - today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); //truncate millis - boolean isValid; - String errMessage; - if (shouldBeFuture) { - today.setTime(today.getTime() - leeway * 1000); - isValid = date == null || !today.after(date); - errMessage = String.format("The Token has expired on %s.", date); - } else { - today.setTime(today.getTime() + leeway * 1000); - isValid = date == null || !today.before(date); - errMessage = String.format("The Token can't be used before %s.", date); - } - if (!isValid) { - throw new InvalidDateException(errMessage); - } - } - - private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } + private final Algorithm algorithm; + final Map claims; + private final Clock clock; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + this.algorithm = algorithm; + this.claims = Collections.unmodifiableMap(claims); + this.clock = clock; + } + + /** + * Initialize a JWTVerifier instance using the given Algorithm. + * + * @param algorithm + * the Algorithm to use on the JWT verification. + * @return a JWTVerifier.Verification instance to configure. + * @throws IllegalArgumentException + * if the provided algorithm is null. + */ + static Verification init(Algorithm algorithm) throws IllegalArgumentException { + return new BaseVerification(algorithm); + } + + /** + * The Verification class holds the Claims required by a JWT to be valid. + */ + public static class BaseVerification implements Verification { + private final Algorithm algorithm; + private final Map claims; + private long defaultLeeway; + + BaseVerification(Algorithm algorithm) throws IllegalArgumentException { + if (algorithm == null) { + throw new IllegalArgumentException("The Algorithm cannot be null."); + } + + this.algorithm = algorithm; + this.claims = new HashMap<>(); + this.defaultLeeway = 0; + } + + /** + * Require a specific Issuer ("iss") claim. + * + * @param issuer + * the required Issuer value + * @return this same Verification instance. + */ + @Override + public Verification withIssuer(String issuer) { + requireClaim(PublicClaims.ISSUER, issuer); + return this; + } + + /** + * Require a specific Subject ("sub") claim. + * + * @param subject + * the required Subject value + * @return this same Verification instance. + */ + @Override + public Verification withSubject(String subject) { + requireClaim(PublicClaims.SUBJECT, subject); + return this; + } + + /** + * Require a specific Audience ("aud") claim. + * + * @param audience + * the required Audience value + * @return this same Verification instance. + */ + @Override + public Verification withAudience(String... audience) { + requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); + return this; + } + + /** + * Define the default window in seconds in which the Not Before, Issued + * At and Expires At Claims will still be valid. Setting a specific + * leeway value on a given Claim will override this value for that + * Claim. + * + * @param leeway + * the window in seconds in which the Not Before, Issued At + * and Expires At Claims will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptLeeway(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + this.defaultLeeway = leeway; + return this; + } + + /** + * Set a specific leeway window in seconds in which the Expires At + * ("exp") Claim will still be valid. Expiration Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Expires At Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.EXPIRES_AT, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Not Before + * ("nbf") Claim will still be valid. Not Before Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Not Before Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.NOT_BEFORE, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Issued At + * ("iat") Claim will still be valid. Issued At Date is always verified + * when the value is present. This method overrides the value set with + * acceptLeeway + * + * @param leeway + * the window in seconds in which the Issued At Claim will + * still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if leeway is negative. + */ + @Override + public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.ISSUED_AT, leeway); + return this; + } + + /** + * Require a specific JWT Id ("jti") claim. + * + * @param jwtId + * the required Id value + * @return this same Verification instance. + */ + @Override + public Verification withJWTId(String jwtId) { + requireClaim(PublicClaims.JWT_ID, jwtId); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name + * the Claim's name. + * @param value + * the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name + * the Claim's name. + * @param items + * the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name + * the Claim's name. + * @param items + * the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException + * if the name is null. + */ + @Override + public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the + * configuration already provided. + * + * @return a new JWTVerifier instance. + */ + @Override + public JWTVerifier build() { + return this.build(new ClockImpl()); + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the + * configuration already provided. ONLY FOR TEST PURPOSES. + * + * @param clock + * the instance that will handle the current time. + * @return a new JWTVerifier instance with a custom Clock. + */ + public JWTVerifier build(Clock clock) { + addLeewayToDateClaims(); + return new JWTVerifier(algorithm, claims, clock); + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } + + private void requireClaim(String name, Object value) { + if (value == null) { + claims.remove(name); + return; + } + claims.put(name, value); + } + } + + /** + * Perform the verification against the given Token, using any previous + * configured options. + * + * @param token + * to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException + * if any of the required contents inside the JWT is invalid. + */ + public DecodedJWT verify(String token) throws JWTVerificationException { + DecodedJWT jwt = JWTDecoder.decode(token); + verifyAlgorithm(jwt, algorithm); + verifySignature(TokenUtils.splitToken(token)); + verifyClaims(jwt, claims); + return jwt; + } + + private void verifySignature(String[] parts) throws SignatureVerificationException { + byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); + byte[] signature = Base64.decodeBase64(parts[2]); + algorithm.verify(content, signature); + } + + private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { + if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { + throw new AlgorithmMismatchException( + "The provided Algorithm doesn't match the one defined in the JWT's Header."); + } + } + + private void verifyClaims(DecodedJWT jwt, Map claims) { + for (Map.Entry entry : claims.entrySet()) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + // noinspection unchecked + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + } + + private void assertValidClaim(Claim claim, String claimName, Object value) { + boolean isValid = false; + if (value instanceof String) { + isValid = value.equals(claim.asString()); + } else if (value instanceof Integer) { + isValid = value.equals(claim.asInt()); + } else if (value instanceof Boolean) { + isValid = value.equals(claim.asBoolean()); + } else if (value instanceof Double) { + isValid = value.equals(claim.asDouble()); + } else if (value instanceof Date) { + isValid = value.equals(claim.asDate()); + } else if (value instanceof Object[]) { + List claimArr = Arrays.asList(claim.as(Object[].class)); + List valueArr = Arrays.asList((Object[]) value); + isValid = claimArr.containsAll(valueArr); + } + + if (!isValid) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidStringClaim(String claimName, String value, String expectedValue) { + if (!expectedValue.equals(value)) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { + Date today = clock.getToday(); + today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate + // millis + if (shouldBeFuture) { + assertDateIsFuture(date, leeway, today); + } else { + assertDateIsPast(date, leeway, today); + } + } + + private void assertDateIsFuture(Date date, long leeway, Date today) { + + today.setTime(today.getTime() - leeway * 1000); + if (date != null && today.after(date)) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", date)); + } + } + + private void assertDateIsPast(Date date, long leeway, Date today) { + today.setTime(today.getTime() + leeway * 1000); + if(date!=null && today.before(date)) { + throw new InvalidClaimException(String.format("The Token can't be used before %s.", date)); + } + + } + + private void assertValidAudienceClaim(List audience, List value) { + if (audience == null || !audience.containsAll(value)) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + } + } } diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java deleted file mode 100644 index eefb0d01..00000000 --- a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidDateException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.auth0.jwt.exceptions; - -public class InvalidDateException extends InvalidClaimException { - private static final long serialVersionUID = -7701609746394348413L; - - public InvalidDateException(String message) { - super(message); - } -} diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java new file mode 100644 index 00000000..19082520 --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java @@ -0,0 +1,10 @@ +package com.auth0.jwt.exceptions; + +public class TokenExpiredException extends JWTVerificationException { + + private static final long serialVersionUID = -7076928975713577708L; + + public TokenExpiredException(String message) { + super(message); + } +} diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b7db94da..6d250916 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; +import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.Rule; @@ -392,7 +393,7 @@ public void shouldValidateExpiresAtIfPresent() throws Exception { @Test public void shouldThrowOnInvalidExpiresAtIfPresent() throws Exception { - exception.expect(InvalidClaimException.class); + exception.expect(TokenExpiredException.class); exception.expectMessage(startsWith("The Token has expired on")); Clock clock = mock(Clock.class); when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE + 1000)); From 6a1755390728eef124da58ed591ee97fb9de7b48 Mon Sep 17 00:00:00 2001 From: Lorenzo Spinelli Date: Fri, 17 Mar 2017 09:06:18 +0100 Subject: [PATCH 038/355] rollbacked code formating changes --- .../main/java/com/auth0/jwt/JWTVerifier.java | 875 ++++++++---------- 1 file changed, 407 insertions(+), 468 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 95996763..3eee60ba 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,15 +1,5 @@ package com.auth0.jwt; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.codec.binary.Base64; - import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; @@ -21,464 +11,413 @@ import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; +import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token - * has not only a proper JWT format, but also it's signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier { - private final Algorithm algorithm; - final Map claims; - private final Clock clock; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { - this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; - } - - /** - * Initialize a JWTVerifier instance using the given Algorithm. - * - * @param algorithm - * the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. - * @throws IllegalArgumentException - * if the provided algorithm is null. - */ - static Verification init(Algorithm algorithm) throws IllegalArgumentException { - return new BaseVerification(algorithm); - } - - /** - * The Verification class holds the Claims required by a JWT to be valid. - */ - public static class BaseVerification implements Verification { - private final Algorithm algorithm; - private final Map claims; - private long defaultLeeway; - - BaseVerification(Algorithm algorithm) throws IllegalArgumentException { - if (algorithm == null) { - throw new IllegalArgumentException("The Algorithm cannot be null."); - } - - this.algorithm = algorithm; - this.claims = new HashMap<>(); - this.defaultLeeway = 0; - } - - /** - * Require a specific Issuer ("iss") claim. - * - * @param issuer - * the required Issuer value - * @return this same Verification instance. - */ - @Override - public Verification withIssuer(String issuer) { - requireClaim(PublicClaims.ISSUER, issuer); - return this; - } - - /** - * Require a specific Subject ("sub") claim. - * - * @param subject - * the required Subject value - * @return this same Verification instance. - */ - @Override - public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); - return this; - } - - /** - * Require a specific Audience ("aud") claim. - * - * @param audience - * the required Audience value - * @return this same Verification instance. - */ - @Override - public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); - return this; - } - - /** - * Define the default window in seconds in which the Not Before, Issued - * At and Expires At Claims will still be valid. Setting a specific - * leeway value on a given Claim will override this value for that - * Claim. - * - * @param leeway - * the window in seconds in which the Not Before, Issued At - * and Expires At Claims will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptLeeway(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - this.defaultLeeway = leeway; - return this; - } - - /** - * Set a specific leeway window in seconds in which the Expires At - * ("exp") Claim will still be valid. Expiration Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Expires At Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Not Before - * ("nbf") Claim will still be valid. Not Before Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Not Before Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); - return this; - } - - /** - * Set a specific leeway window in seconds in which the Issued At - * ("iat") Claim will still be valid. Issued At Date is always verified - * when the value is present. This method overrides the value set with - * acceptLeeway - * - * @param leeway - * the window in seconds in which the Issued At Claim will - * still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if leeway is negative. - */ - @Override - public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { - assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); - return this; - } - - /** - * Require a specific JWT Id ("jti") claim. - * - * @param jwtId - * the required Id value - * @return this same Verification instance. - */ - @Override - public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Integer value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Double value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, String value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Claim value. - * - * @param name - * the Claim's name. - * @param value - * the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withClaim(String name, Date value) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, value); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name - * the Claim's name. - * @param items - * the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name - * the Claim's name. - * @param items - * the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException - * if the name is null. - */ - @Override - public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { - assertNonNull(name); - requireClaim(name, items); - return this; - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the - * configuration already provided. - * - * @return a new JWTVerifier instance. - */ - @Override - public JWTVerifier build() { - return this.build(new ClockImpl()); - } - - /** - * Creates a new and reusable instance of the JWTVerifier with the - * configuration already provided. ONLY FOR TEST PURPOSES. - * - * @param clock - * the instance that will handle the current time. - * @return a new JWTVerifier instance with a custom Clock. - */ - public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); - } - - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); - } - } - - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); - } - } - - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - } - - private void requireClaim(String name, Object value) { - if (value == null) { - claims.remove(name); - return; - } - claims.put(name, value); - } - } - - /** - * Perform the verification against the given Token, using any previous - * configured options. - * - * @param token - * to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException - * if any of the required contents inside the JWT is invalid. - */ - public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWTDecoder.decode(token); - verifyAlgorithm(jwt, algorithm); - verifySignature(TokenUtils.splitToken(token)); - verifyClaims(jwt, claims); - return jwt; - } - - private void verifySignature(String[] parts) throws SignatureVerificationException { - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = Base64.decodeBase64(parts[2]); - algorithm.verify(content, signature); - } - - private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { - if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException( - "The provided Algorithm doesn't match the one defined in the JWT's Header."); - } - } - - private void verifyClaims(DecodedJWT jwt, Map claims) { - for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - // noinspection unchecked - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; - } - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Date) { - isValid = value.equals(claim.asDate()); - } else if (value instanceof Object[]) { - List claimArr = Arrays.asList(claim.as(Object[].class)); - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { + private final Algorithm algorithm; + final Map claims; + private final Clock clock; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + this.algorithm = algorithm; + this.claims = Collections.unmodifiableMap(claims); + this.clock = clock; + } + + /** + * Initialize a JWTVerifier instance using the given Algorithm. + * + * @param algorithm the Algorithm to use on the JWT verification. + * @return a JWTVerifier.Verification instance to configure. + * @throws IllegalArgumentException if the provided algorithm is null. + */ + static Verification init(Algorithm algorithm) throws IllegalArgumentException { + return new BaseVerification(algorithm); + } + + /** + * The Verification class holds the Claims required by a JWT to be valid. + */ + public static class BaseVerification implements Verification { + private final Algorithm algorithm; + private final Map claims; + private long defaultLeeway; + + BaseVerification(Algorithm algorithm) throws IllegalArgumentException { + if (algorithm == null) { + throw new IllegalArgumentException("The Algorithm cannot be null."); + } + + this.algorithm = algorithm; + this.claims = new HashMap<>(); + this.defaultLeeway = 0; + } + + /** + * Require a specific Issuer ("iss") claim. + * + * @param issuer the required Issuer value + * @return this same Verification instance. + */ + @Override + public Verification withIssuer(String issuer) { + requireClaim(PublicClaims.ISSUER, issuer); + return this; + } + + /** + * Require a specific Subject ("sub") claim. + * + * @param subject the required Subject value + * @return this same Verification instance. + */ + @Override + public Verification withSubject(String subject) { + requireClaim(PublicClaims.SUBJECT, subject); + return this; + } + + /** + * Require a specific Audience ("aud") claim. + * + * @param audience the required Audience value + * @return this same Verification instance. + */ + @Override + public Verification withAudience(String... audience) { + requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); + return this; + } + + /** + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * Setting a specific leeway value on a given Claim will override this value for that Claim. + * + * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptLeeway(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + this.defaultLeeway = leeway; + return this; + } + + /** + * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. + * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Expires At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.EXPIRES_AT, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. + * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Not Before Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.NOT_BEFORE, leeway); + return this; + } + + /** + * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. + * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Issued At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ + @Override + public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { + assertPositive(leeway); + requireClaim(PublicClaims.ISSUED_AT, leeway); + return this; + } + + /** + * Require a specific JWT Id ("jti") claim. + * + * @param jwtId the required Id value + * @return this same Verification instance. + */ + @Override + public Verification withJWTId(String jwtId) { + requireClaim(PublicClaims.JWT_ID, jwtId); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Integer value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Double value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, String value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Date value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * + * @return a new JWTVerifier instance. + */ + @Override + public JWTVerifier build() { + return this.build(new ClockImpl()); + } + + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * ONLY FOR TEST PURPOSES. + * + * @param clock the instance that will handle the current time. + * @return a new JWTVerifier instance with a custom Clock. + */ + public JWTVerifier build(Clock clock) { + addLeewayToDateClaims(); + return new JWTVerifier(algorithm, claims, clock); + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); + } + } + + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } + + private void requireClaim(String name, Object value) { + if (value == null) { + claims.remove(name); + return; + } + claims.put(name, value); + } + } + + + /** + * Perform the verification against the given Token, using any previous configured options. + * + * @param token to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. + */ + public DecodedJWT verify(String token) throws JWTVerificationException { + DecodedJWT jwt = JWTDecoder.decode(token); + verifyAlgorithm(jwt, algorithm); + verifySignature(TokenUtils.splitToken(token)); + verifyClaims(jwt, claims); + return jwt; + } + + private void verifySignature(String[] parts) throws SignatureVerificationException { + byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); + byte[] signature = Base64.decodeBase64(parts[2]); + algorithm.verify(content, signature); + } + + private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { + if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { + throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); + } + } + + private void verifyClaims(DecodedJWT jwt, Map claims) { + for (Map.Entry entry : claims.entrySet()) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + //noinspection unchecked + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + } + + private void assertValidClaim(Claim claim, String claimName, Object value) { + boolean isValid = false; + if (value instanceof String) { + isValid = value.equals(claim.asString()); + } else if (value instanceof Integer) { + isValid = value.equals(claim.asInt()); + } else if (value instanceof Boolean) { + isValid = value.equals(claim.asBoolean()); + } else if (value instanceof Double) { + isValid = value.equals(claim.asDouble()); + } else if (value instanceof Date) { + isValid = value.equals(claim.asDate()); + } else if (value instanceof Object[]) { + List claimArr = Arrays.asList(claim.as(Object[].class)); + List valueArr = Arrays.asList((Object[]) value); + isValid = claimArr.containsAll(valueArr); + } + + if (!isValid) { + throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidStringClaim(String claimName, String value, String expectedValue) { + if (!expectedValue.equals(value)) { + throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + } + } + + private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { Date today = clock.getToday(); today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate // millis @@ -505,9 +444,9 @@ private void assertDateIsPast(Date date, long leeway, Date today) { } - private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } + private void assertValidAudienceClaim(List audience, List value) { + if (audience == null || !audience.containsAll(value)) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + } + } } From d27b1215ef1652f5563f47243f4b093615c187de Mon Sep 17 00:00:00 2001 From: vrancic Date: Fri, 31 Mar 2017 00:43:51 +0200 Subject: [PATCH 039/355] Long type is missing for claims. I added Long type for claims and updated tests. --- .../main/java/com/auth0/jwt/JWTVerifier.java | 2 ++ .../java/com/auth0/jwt/impl/JsonNodeClaim.java | 3 +++ .../main/java/com/auth0/jwt/impl/NullClaim.java | 5 +++++ .../java/com/auth0/jwt/interfaces/Claim.java | 8 ++++++++ .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 17 +++++++++++++++++ 5 files changed, 35 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 0ee93f76..46f26e98 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -393,6 +393,8 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { isValid = value.equals(claim.asString()); } else if (value instanceof Integer) { isValid = value.equals(claim.asInt()); + } else if (value instanceof Long) { + isValid = value.equals(claim.asLong()); } else if (value instanceof Boolean) { isValid = value.equals(claim.asBoolean()); } else if (value instanceof Double) { 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 317b4946..67355331 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -34,6 +34,9 @@ public Integer asInt() { return !data.isNumber() ? null : data.asInt(); } + @Override + public Long asLong() { return !data.isNumber() ? null : data.asLong(); } + @Override public Double asDouble() { return !data.isNumber() ? null : data.asDouble(); diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 755ef64d..e403100e 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -25,6 +25,11 @@ public Integer asInt() { return null; } + @Override + public Long asLong() { + return null; + } + @Override public Double asDouble() { return null; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 28c158f1..1b58f9ca 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -28,6 +28,14 @@ public interface Claim { */ Integer asInt(); + /** + * Get this Claim as an Long. + * If the value isn't of type Long or it can't be converted to an Long, null will be returned. + * + * @return the value as an Long or null. + */ + Long asLong(); + /** * Get this Claim as a Double. * If the value isn't of type Double or it can't be converted to a Double, null will be returned. 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 7a5dbc35..0ee0c71b 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -69,6 +69,23 @@ public void shouldGetNullIntIfNotIntValue() throws Exception { assertThat(claimFromNode(stringValue).asInt(), is(nullValue())); } + @Test + public void shouldGetLongValue() throws Exception { + JsonNode value = mapper.valueToTree(Long.MAX_VALUE); + Claim claim = claimFromNode(value); + + assertThat(claim.asLong(), is(notNullValue())); + assertThat(claim.asLong(), is(Long.MAX_VALUE)); + } + + @Test + public void shouldGetNullLongIfNotIntValue() throws Exception { + JsonNode objectValue = mapper.valueToTree(new Object()); + assertThat(claimFromNode(objectValue).asLong(), is(nullValue())); + JsonNode stringValue = mapper.valueToTree("" + Long.MAX_VALUE); + assertThat(claimFromNode(stringValue).asLong(), is(nullValue())); + } + @Test public void shouldGetDoubleValue() throws Exception { JsonNode value = mapper.valueToTree(1.5); From 6825cd3b549ddf249d3458494c4e47827c2d1f56 Mon Sep 17 00:00:00 2001 From: vrancic Date: Tue, 4 Apr 2017 00:07:49 +0300 Subject: [PATCH 040/355] Tests for Long type updated. --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 15 +++++++++++++++ .../com/auth0/jwt/interfaces/Verification.java | 2 ++ .../test/java/com/auth0/jwt/JWTVerifierTest.java | 11 +++++++++++ .../java/com/auth0/jwt/impl/NullClaimTest.java | 5 +++++ 4 files changed, 33 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 46f26e98..3d00ba59 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -197,6 +197,21 @@ public Verification withClaim(String name, Integer value) throws IllegalArgument return this; } + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withClaim(String name, Long value) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, value); + return this; + } + /** * Require a specific Claim value. * diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 3d2ac60f..465ebe5d 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -25,6 +25,8 @@ public interface Verification { Verification withClaim(String name, Integer value) throws IllegalArgumentException; + Verification withClaim(String name, Long value) throws IllegalArgumentException; + Verification withClaim(String name, Double value) throws IllegalArgumentException; Verification withClaim(String name, String value) throws IllegalArgumentException; diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b7db94da..4b55d2a8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -222,6 +222,17 @@ public void shouldValidateCustomClaimOfTypeInteger() throws Exception { assertThat(jwt, is(notNullValue())); } + @Test + public void shouldValidateCustomClaimOfTypeLong() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjo5MjIzMzcyMDM2ODU0Nzc2MDB9.km-IwQ5IDnTZFmuJzhSgvjTzGkn_Z5X29g4nAuVC56I"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", 922337203685477600L) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + @Test public void shouldValidateCustomClaimOfTypeDouble() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoyMy40NX0.7pyX2OmEGaU9q15T8bGFqRm-d3RVTYnqmZNZtxMKSlA"; diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index 9796c322..94b1d4cd 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -31,6 +31,11 @@ public void shouldGetAsInt() throws Exception { assertThat(claim.asInt(), is(nullValue())); } + @Test + public void shouldGetAsLong() throws Exception { + assertThat(claim.asLong(), is(nullValue())); + } + @Test public void shouldGetAsDouble() throws Exception { assertThat(claim.asDouble(), is(nullValue())); From c60687cd4a018c6b87c72b52c3c0419aa30d8014 Mon Sep 17 00:00:00 2001 From: vrancic Date: Thu, 6 Apr 2017 23:37:34 +0300 Subject: [PATCH 041/355] JWTCreator extended with Long type. --- .../main/java/com/auth0/jwt/JWTCreator.java | 29 ++++++++++++++++++- .../java/com/auth0/jwt/JWTCreatorTest.java | 22 ++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 23d87dc4..fa93be95 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.commons.codec.binary.Base64; @@ -192,6 +191,20 @@ public Builder withClaim(String name, Integer value) throws IllegalArgumentExcep return this; } + /** + * Add a custom Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, Long value) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, value); + return this; + } + /** * Add a custom Claim value. * @@ -262,6 +275,20 @@ public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgume return this; } + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + /** * Creates a new JWT and signs is with the given algorithm * diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 43d336bb..a4d5204c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -204,6 +204,17 @@ public void shouldAcceptCustomClaimOfTypeInteger() throws Exception { assertThat(parts[1], is("eyJuYW1lIjoxMjN9")); } + @Test + public void shouldAcceptCustomClaimOfTypeLong() throws Exception { + String jwt = JWTCreator.init() + .withClaim("name", Long.MAX_VALUE) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjo5MjIzMzcyMDM2ODU0Nzc1ODA3fQ")); + } + @Test public void shouldAcceptCustomClaimOfTypeDouble() throws Exception { String jwt = JWTCreator.init() @@ -259,4 +270,15 @@ public void shouldAcceptCustomArrayClaimOfTypeInteger() throws Exception { String[] parts = jwt.split("\\."); assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); } + + @Test + public void shouldAcceptCustomArrayClaimOfTypeLong() throws Exception { + String jwt = JWTCreator.init() + .withArrayClaim("name", new Long[]{1L, 2L, 3L}) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); + } } \ No newline at end of file From 9a496ad7fb963a2ad4c7279bf71164dc75853d7d Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 7 Apr 2017 16:56:19 -0300 Subject: [PATCH 042/355] fix claim.isNull() method --- .../com/auth0/jwt/impl/JsonNodeClaim.java | 2 +- .../java/com/auth0/jwt/JWTDecoderTest.java | 4 +- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 79 +++++++++++++++++-- 3 files changed, 76 insertions(+), 9 deletions(-) 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 317b4946..8e805aa0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -102,7 +102,7 @@ public T as(Class tClazz) throws JWTDecodeException { @Override public boolean isNull() { - return !(data.isArray() || data.canConvertToLong() || data.isTextual() || data.isNumber() || data.isBoolean()); + return !(data.isObject() || data.isArray() || data.canConvertToLong() || data.isTextual() || data.isNumber() || data.isBoolean()); } /** diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 792a3735..cef57b54 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -192,11 +192,11 @@ public void shouldGetValidClaim() throws Exception { } @Test - public void shouldGetNullClaimIfClaimValueIsNull() throws Exception { + public void shouldNotGetNullClaimIfClaimIsEmptyObject() throws Exception { DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnt9fQ.d3nUeeL_69QsrHL0ZWij612LHEQxD8EZg1rNoY3a4aI"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("object"), is(notNullValue())); - assertThat(jwt.getClaim("object").isNull(), is(true)); + assertThat(jwt.getClaim("object").isNull(), is(false)); } @Test 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 7a5dbc35..343aea74 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -12,10 +12,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Map; +import java.util.*; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static com.auth0.jwt.impl.JsonNodeClaim.claimFromNode; @@ -268,12 +265,82 @@ public void shouldReturnBaseClaimWhenParsingNullValue() throws Exception { } @Test - public void shouldReturnValidButNullClaimIfTreeIsEmpty() throws Exception { + public void shouldReturnNonNullClaimWhenParsingObject() throws Exception { JsonNode value = mapper.valueToTree(new Object()); Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); - assertThat(claim.isNull(), is(true)); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingArray() throws Exception { + JsonNode value = mapper.valueToTree(new String[]{}); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingList() throws Exception { + JsonNode value = mapper.valueToTree(new ArrayList()); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingStringValue() throws Exception { + JsonNode value = mapper.valueToTree(""); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingIntValue() throws Exception { + JsonNode value = mapper.valueToTree(Integer.MAX_VALUE); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingDoubleValue() throws Exception { + JsonNode value = mapper.valueToTree(Double.MAX_VALUE); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingDateValue() throws Exception { + JsonNode value = mapper.valueToTree(new Date()); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); + } + + @Test + public void shouldReturnNonNullClaimWhenParsingBooleanValue() throws Exception { + JsonNode value = mapper.valueToTree(Boolean.TRUE); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(false)); } } \ No newline at end of file From 283b89980a495f96f180581e35c68c00d13c5f59 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 11 Apr 2017 14:05:02 -0300 Subject: [PATCH 043/355] make JsonNodeClaim always return false for isNull invocation --- lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java | 2 +- lib/src/main/java/com/auth0/jwt/interfaces/Claim.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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 8e805aa0..d60530cb 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -102,7 +102,7 @@ public T as(Class tClazz) throws JWTDecodeException { @Override public boolean isNull() { - return !(data.isObject() || data.isArray() || data.canConvertToLong() || data.isTextual() || data.isNumber() || data.isBoolean()); + return false; } /** diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 28c158f1..e36c1698 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -10,6 +10,11 @@ */ public interface Claim { + /** + * Whether this Claim has a null value or not. + * + * @return whether this Claim has a null value or not. + */ boolean isNull(); /** From 522f279ac786bd534be2c2ff71e1a85d5897402a Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 14 Mar 2017 19:18:54 -0300 Subject: [PATCH 044/355] allow to get a Claim as Map --- README.md | 1 + .../main/java/com/auth0/jwt/JWTDecoder.java | 1 - .../com/auth0/jwt/impl/JsonNodeClaim.java | 13 ++++++++ .../java/com/auth0/jwt/impl/NullClaim.java | 6 ++++ .../java/com/auth0/jwt/interfaces/Claim.java | 9 ++++++ .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 31 +++++++++++++++++++ .../com/auth0/jwt/impl/NullClaimTest.java | 5 +++ 7 files changed, 65 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b9ec2ae..1a056400 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,7 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. * **as(class)**: Returns the value parsed as **Class Type**. For collections you should use the `asArray` and `asList` methods. +* **asMap()**: Returns the value parsed as **Map**. * **asArray(class)**: Returns the value parsed as an Array of elements of type **Class Type**, or null if the value isn't a JSON Array. * **asList(class)**: Returns the value parsed as a List of elements of type **Class Type**, or null if the value isn't a JSON Array. diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index b7540287..17b47d65 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -3,7 +3,6 @@ import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.impl.JWTParser; import com.auth0.jwt.interfaces.Claim; -import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Header; import com.auth0.jwt.interfaces.Payload; import org.apache.commons.codec.binary.Base64; 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 d60530cb..86eaf5e8 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -3,6 +3,7 @@ import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -90,6 +91,18 @@ public List asList(Class tClazz) throws JWTDecodeException { return list; } + @Override + public Map asMap() throws JWTDecodeException { + ObjectMapper mapper = new ObjectMapper(); + try { + TypeReference> mapType = new TypeReference>() { + }; + return mapper.treeAsTokens(data).readValueAs(mapType); + } catch (IOException e) { + throw new JWTDecodeException("Couldn't map the Claim value to Map", e); + } + } + @Override public T as(Class tClazz) throws JWTDecodeException { ObjectMapper mapper = new ObjectMapper(); diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 755ef64d..90548766 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -5,6 +5,7 @@ import java.util.Date; import java.util.List; +import java.util.Map; /** * The {@link NullClaim} class is a Claim implementation that returns null when any of it's methods it's called. @@ -50,6 +51,11 @@ public List asList(Class tClazz) throws JWTDecodeException { return null; } + @Override + public Map asMap() throws JWTDecodeException { + return null; + } + @Override public T as(Class tClazz) throws JWTDecodeException { return null; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index e36c1698..b7fb3b31 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -4,6 +4,7 @@ import java.util.Date; import java.util.List; +import java.util.Map; /** * The Claim class holds the value in a generic way so that it can be recovered in many representations. @@ -75,6 +76,14 @@ public interface Claim { */ List asList(Class tClazz) throws JWTDecodeException; + /** + * Get this Claim as a generic Map of values. + * + * @return the value as instance of Map. + * @throws JWTDecodeException if the value can't be converted to a Map. + */ + Map asMap() throws JWTDecodeException; + /** * Get this Claim as a custom type T. * 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 343aea74..aae816f8 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; +import org.hamcrest.collection.IsMapContaining; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -203,6 +204,36 @@ public void shouldThrowIfListClassMismatch() throws Exception { claim.asList(UserPojo.class); } + @Test + public void shouldGetMapValue() throws Exception { + Map map = new HashMap<>(); + map.put("text", "extraValue"); + map.put("number", 12); + map.put("boolean", true); + map.put("object", Collections.singletonMap("something", "else")); + + JsonNode value = mapper.valueToTree(map); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + Map backMap = claim.asMap(); + assertThat(backMap, is(notNullValue())); + assertThat(backMap, hasEntry("text", (Object) "extraValue")); + assertThat(backMap, hasEntry("number", (Object) 12)); + assertThat(backMap, hasEntry("boolean", (Object) true)); + assertThat(backMap, hasKey("object")); + assertThat((Map) backMap.get("object"), IsMapContaining.hasEntry("something", (Object) "else")); + } + + @Test + public void shouldThrowIfMapClassMismatch() throws Exception { + JsonNode value = mapper.valueToTree("text node"); + Claim claim = claimFromNode(value); + + exception.expect(JWTDecodeException.class); + claim.asMap(); + } + @Test public void shouldGetCustomClassValue() throws Exception { JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index 9796c322..eaad864b 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -56,6 +56,11 @@ public void shouldGetAsList() throws Exception { assertThat(claim.asList(Object.class), is(nullValue())); } + @Test + public void shouldGetAsMap() throws Exception { + assertThat(claim.asMap(), is(nullValue())); + } + @Test public void shouldGetAsCustomClass() throws Exception { assertThat(claim.as(Object.class), is(nullValue())); From 0a0e83a6cc71b00a3721986da8f2b0e1e2cd53f2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 11 Apr 2017 14:50:17 -0300 Subject: [PATCH 045/355] add decode tests --- .../com/auth0/jwt/impl/JsonNodeClaim.java | 4 ++ .../java/com/auth0/jwt/JWTDecoderTest.java | 61 +++++++++++++++++++ .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 27 +++++++- 3 files changed, 90 insertions(+), 2 deletions(-) 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 86eaf5e8..0d71e906 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -93,6 +93,10 @@ public List asList(Class tClazz) throws JWTDecodeException { @Override public Map asMap() throws JWTDecodeException { + if (!data.isObject()) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); try { TypeReference> mapType = new TypeReference>() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index cef57b54..f96a457b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -7,6 +7,7 @@ import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -199,6 +200,66 @@ public void shouldNotGetNullClaimIfClaimIsEmptyObject() throws Exception { assertThat(jwt.getClaim("object").isNull(), is(false)); } + @Test + public void shouldGetCustomClaimOfTypeInteger() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoxMjN9.XZAudnA7h3_Al5kJydzLjw6RzZC3Q6OvnLEYlhNW7HA"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asInt(), is(123)); + } + + @Test + public void shouldGetCustomClaimOfTypeDouble() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoyMy40NX0.7pyX2OmEGaU9q15T8bGFqRm-d3RVTYnqmZNZtxMKSlA"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asDouble(), is(23.45)); + } + + @Test + public void shouldGetCustomClaimOfTypeBoolean() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjp0cnVlfQ.FwQ8VfsZNRqBa9PXMinSIQplfLU4-rkCLfIlTLg_MV0"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asBoolean(), is(true)); + } + + @Test + public void shouldGetCustomClaimOfTypeDate() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoxNDc4ODkxNTIxfQ.mhioumeok8fghQEhTKF3QtQAksSvZ_9wIhJmgZLhJ6c"; + Date date = new Date(1478891521000L); + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asDate().getTime(), is(date.getTime())); + } + + @Test + public void shouldGetCustomArrayClaimOfTypeString() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.lxM8EcmK1uSZRAPd0HUhXGZJdauRmZmLjoeqz4J9yAA"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asArray(String.class), arrayContaining("text", "123", "true")); + } + + @Test + public void shouldGetCustomArrayClaimOfTypeInteger() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Assert.assertThat(jwt.getClaim("name").asArray(Integer.class), arrayContaining(1, 2, 3)); + } + + @Test + public void shouldGetCustomMapClaim() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InN0cmluZyI6InZhbHVlIiwibnVtYmVyIjoxLCJib29sZWFuIjp0cnVlfX0.-8aIaXd2-rp1lLuDEQmCeisCBX9X_zbqdPn2llGxNoc"; + DecodedJWT jwt = JWT.decode(token); + Assert.assertThat(jwt, is(notNullValue())); + Map map = jwt.getClaim("name").asMap(); + Assert.assertThat(map, hasEntry("string", (Object) "value")); + Assert.assertThat(map, hasEntry("number", (Object) 1)); + Assert.assertThat(map, hasEntry("boolean", (Object) true)); + } + @Test public void shouldGetAvailableClaims() throws Exception { DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTIzNDU2Nzg5MCIsIm5iZiI6IjEyMzQ1Njc4OTAiLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.TX9Ct4feGp9YyeGK9Zl91tO0YBOrguJ4As9jeqgHdZQ"); 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 aae816f8..37760140 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -5,14 +5,17 @@ import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.hamcrest.collection.IsMapContaining; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.IOException; import java.util.*; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; @@ -21,6 +24,8 @@ 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; public class JsonNodeClaimTest { @@ -204,6 +209,22 @@ public void shouldThrowIfListClassMismatch() throws Exception { claim.asList(UserPojo.class); } + @Test + public void shouldGetNullMapIfNullValue() throws Exception { + JsonNode value = mapper.valueToTree(null); + Claim claim = claimFromNode(value); + + assertThat(claim.asMap(), is(nullValue())); + } + + @Test + public void shouldGetNullMapIfNonArrayValue() throws Exception { + JsonNode value = mapper.valueToTree(1); + Claim claim = claimFromNode(value); + + assertThat(claim.asMap(), is(nullValue())); + } + @Test public void shouldGetMapValue() throws Exception { Map map = new HashMap<>(); @@ -226,8 +247,10 @@ public void shouldGetMapValue() throws Exception { } @Test - public void shouldThrowIfMapClassMismatch() throws Exception { - JsonNode value = mapper.valueToTree("text node"); + 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); exception.expect(JWTDecodeException.class); From c5c3d9f9ed803d6d8859461e333d4456df613d01 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 15 Mar 2017 15:34:53 -0300 Subject: [PATCH 046/355] remove travis. add circle ci badge --- .travis.yml | 22 ---------------------- README.md | 6 +++--- 2 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c66eeaf6..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: java -jdk: - - openjdk7 - - oraclejdk7 - - oraclejdk8 -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -sudo: false -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ -script: - - "./gradlew clean check jacocoTestReport --continue" -after_success: - - bash <(curl -s https://codecov.io/bash) -after_failure: - - cat $HOME/travis/build/auth0/java-jwt/lib/build/reports/tests/index.html -branches: - only: - - master \ No newline at end of file diff --git a/README.md b/README.md index 1a056400..a5ee7da7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Java JWT -[![Build Status](https://travis-ci.org/auth0/java-jwt.svg?branch=v3)](https://travis-ci.org/auth0/java-jwt) +[![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt/v3.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) @@ -216,7 +216,7 @@ Additional Claims defined in the token's Header can be obtained by calling `getH Claim claim = jwt.getHeaderClaim("owner"); ``` -When creating a Token with the `JWT.create()` you can specify header Claims by calling `withHeader()` and passing both the map of claims. +When creating a Token with the `JWT.create()` you can specify header Claims by calling `withHeader()` and passing both the map of claims. ```java Map headerClaims = new HashMap(); @@ -302,7 +302,7 @@ or Claim claim = jwt.getClaim("isAdmin"); ``` -When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. +When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. ```java String token = JWT.create() From 5130ec9efc48a75e2f8455931fd6923e08405e0e Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 12 Apr 2017 15:25:14 -0300 Subject: [PATCH 047/355] add get Claim as Long in the README.md file --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5ee7da7..0a9fb466 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,7 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai * **asBoolean()**: Returns the Boolean value or null if it can't be converted. * **asInt()**: Returns the Integer value or null if it can't be converted. * **asDouble()**: Returns the Double value or null if it can't be converted. +* **asLong()**: Returns the Long value or null if it can't be converted. * **asString()**: Returns the String value or null if it can't be converted. * **asDate()**: Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. From 72bceb977586a1736035ccc74220d14bc466f0c4 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 19 Apr 2017 17:13:20 -0300 Subject: [PATCH 048/355] change base class of JWTDecoder --- lib/src/main/java/com/auth0/jwt/JWT.java | 2 +- .../main/java/com/auth0/jwt/JWTDecoder.java | 3 +- .../jwt/exceptions/TokenExpiredException.java | 10 ++--- .../com/auth0/jwt/impl/JsonNodeClaim.java | 4 +- .../java/com/auth0/jwt/JWTDecoderTest.java | 42 +++++++++---------- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index 6547f8d9..f8e5cfc3 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -6,7 +6,7 @@ import com.auth0.jwt.interfaces.Verification; @SuppressWarnings("WeakerAccess") -public abstract class JWT implements DecodedJWT { +public abstract class JWT { /** * Decode a given Json Web Token. diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 17b47d65..1f1eed7b 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -3,6 +3,7 @@ import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.impl.JWTParser; import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Header; import com.auth0.jwt.interfaces.Payload; import org.apache.commons.codec.binary.Base64; @@ -16,7 +17,7 @@ * The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation. */ @SuppressWarnings("WeakerAccess") -final class JWTDecoder extends JWT { +final class JWTDecoder implements DecodedJWT { private final String token; private Header header; diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java index 19082520..f5d2e67a 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java @@ -1,10 +1,10 @@ package com.auth0.jwt.exceptions; public class TokenExpiredException extends JWTVerificationException { - - private static final long serialVersionUID = -7076928975713577708L; - public TokenExpiredException(String message) { - super(message); - } + private static final long serialVersionUID = -7076928975713577708L; + + public TokenExpiredException(String message) { + super(message); + } } 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 e4a85360..0f27656a 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -36,7 +36,9 @@ public Integer asInt() { } @Override - public Long asLong() { return !data.isNumber() ? null : data.asLong(); } + public Long asLong() { + return !data.isNumber() ? null : data.asLong(); + } @Override public Double asDouble() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index f96a457b..755ff3f0 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -25,7 +25,7 @@ public class JWTDecoderTest { @Test public void getSubject() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"); assertThat(jwt.getSubject(), is(notNullValue())); assertThat(jwt.getSubject(), is("1234567890")); } @@ -35,14 +35,14 @@ public void getSubject() throws Exception { public void shouldThrowIfLessThan3Parts() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("The token was expected to have 3 parts, but got 2."); - JWTDecoder.decode("two.parts"); + JWT.decode("two.parts"); } @Test public void shouldThrowIfMoreThan3Parts() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("The token was expected to have 3 parts, but got 4."); - JWTDecoder.decode("this.has.four.parts"); + JWT.decode("this.has.four.parts"); } @Test @@ -66,7 +66,7 @@ public void shouldThrowIfHeaderHasInvalidJSONFormat() throws Exception { // getToken @Test public void shouldGetStringToken() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getToken(), is(notNullValue())); assertThat(jwt.getToken(), is("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); @@ -76,14 +76,14 @@ public void shouldGetStringToken() throws Exception { @Test public void shouldGetAlgorithm() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getAlgorithm(), is("HS256")); } @Test public void shouldGetSignature() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getSignature(), is("XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); } @@ -92,21 +92,21 @@ public void shouldGetSignature() throws Exception { @Test public void shouldGetIssuer() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIERvZSJ9.SgXosfRR_IwCgHq5lF3tlM-JHtpucWCRSaVuoHTbWbQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIERvZSJ9.SgXosfRR_IwCgHq5lF3tlM-JHtpucWCRSaVuoHTbWbQ"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getIssuer(), is("John Doe")); } @Test public void shouldGetSubject() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJUb2szbnMifQ.RudAxkslimoOY3BLl2Ghny3BrUKu9I1ZrXzCZGDJtNs"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJUb2szbnMifQ.RudAxkslimoOY3BLl2Ghny3BrUKu9I1ZrXzCZGDJtNs"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getSubject(), is("Tok3ns")); } @Test public void shouldGetArrayAudience() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiSG9wZSIsIlRyYXZpcyIsIlNvbG9tb24iXX0.Tm4W8WnfPjlmHSmKFakdij0on2rWPETpoM7Sh0u6-S4"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiSG9wZSIsIlRyYXZpcyIsIlNvbG9tb24iXX0.Tm4W8WnfPjlmHSmKFakdij0on2rWPETpoM7Sh0u6-S4"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getAudience(), is(IsCollectionWithSize.hasSize(3))); assertThat(jwt.getAudience(), is(IsCollectionContaining.hasItems("Hope", "Travis", "Solomon"))); @@ -114,7 +114,7 @@ public void shouldGetArrayAudience() throws Exception { @Test public void shouldGetStringAudience() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJKYWNrIFJleWVzIn0.a4I9BBhPt1OB1GW67g2P1bEHgi6zgOjGUL4LvhE9Dgc"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJKYWNrIFJleWVzIn0.a4I9BBhPt1OB1GW67g2P1bEHgi6zgOjGUL4LvhE9Dgc"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getAudience(), is(IsCollectionWithSize.hasSize(1))); assertThat(jwt.getAudience(), is(IsCollectionContaining.hasItems("Jack Reyes"))); @@ -122,7 +122,7 @@ public void shouldGetStringAudience() throws Exception { @Test public void shouldGetExpirationTime() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzY3MjcwODZ9.L9dcPHEDQew2u9MkDCORFkfDGcSOsgoPqNY-LUMLEHg"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzY3MjcwODZ9.L9dcPHEDQew2u9MkDCORFkfDGcSOsgoPqNY-LUMLEHg"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getExpiresAt(), is(instanceOf(Date.class))); long ms = 1476727086L * 1000; @@ -133,7 +133,7 @@ public void shouldGetExpirationTime() throws Exception { @Test public void shouldGetNotBefore() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0NzY3MjcwODZ9.tkpD3iCPQPVqjnjpDVp2bJMBAgpVCG9ZjlBuMitass0"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0NzY3MjcwODZ9.tkpD3iCPQPVqjnjpDVp2bJMBAgpVCG9ZjlBuMitass0"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getNotBefore(), is(instanceOf(Date.class))); long ms = 1476727086L * 1000; @@ -144,7 +144,7 @@ public void shouldGetNotBefore() throws Exception { @Test public void shouldGetIssuedAt() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NzY3MjcwODZ9.KPjGoW665E8V5_27Jugab8qSTxLk2cgquhPCBfAP0_w"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NzY3MjcwODZ9.KPjGoW665E8V5_27Jugab8qSTxLk2cgquhPCBfAP0_w"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getIssuedAt(), is(instanceOf(Date.class))); long ms = 1476727086L * 1000; @@ -155,21 +155,21 @@ public void shouldGetIssuedAt() throws Exception { @Test public void shouldGetId() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkwIn0.m3zgEfVUFOd-CvL3xG5BuOWLzb0zMQZCqiVNQQOPOvA"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkwIn0.m3zgEfVUFOd-CvL3xG5BuOWLzb0zMQZCqiVNQQOPOvA"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getId(), is("1234567890")); } @Test public void shouldGetContentType() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsImN0eSI6ImF3ZXNvbWUifQ.e30.AIm-pJDOaAyct9qKMlN-lQieqNDqc3d4erqUZc5SHAs"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsImN0eSI6ImF3ZXNvbWUifQ.e30.AIm-pJDOaAyct9qKMlN-lQieqNDqc3d4erqUZc5SHAs"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getContentType(), is("awesome")); } @Test public void shouldGetType() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.e30.WdFmrzx8b9v_a-r6EHC2PTAaWywgm_8LiP8RBRhYwkI"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.e30.WdFmrzx8b9v_a-r6EHC2PTAaWywgm_8LiP8RBRhYwkI"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getType(), is("JWS")); } @@ -178,7 +178,7 @@ public void shouldGetType() throws Exception { @Test public void shouldGetMissingClaimIfClaimDoesNotExist() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.e30.K17vlwhE8FCMShdl1_65jEYqsQqBOVMPUU9IgG-QlTM"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.K17vlwhE8FCMShdl1_65jEYqsQqBOVMPUU9IgG-QlTM"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("notExisting"), is(notNullValue())); assertThat(jwt.getClaim("notExisting"), is(instanceOf(NullClaim.class))); @@ -186,7 +186,7 @@ public void shouldGetMissingClaimIfClaimDoesNotExist() throws Exception { @Test public void shouldGetValidClaim() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnsibmFtZSI6ImpvaG4ifX0.lrU1gZlOdlmTTeZwq0VI-pZx2iV46UWYd5-lCjy6-c4"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnsibmFtZSI6ImpvaG4ifX0.lrU1gZlOdlmTTeZwq0VI-pZx2iV46UWYd5-lCjy6-c4"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("object"), is(notNullValue())); assertThat(jwt.getClaim("object"), is(instanceOf(Claim.class))); @@ -194,7 +194,7 @@ public void shouldGetValidClaim() throws Exception { @Test public void shouldNotGetNullClaimIfClaimIsEmptyObject() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnt9fQ.d3nUeeL_69QsrHL0ZWij612LHEQxD8EZg1rNoY3a4aI"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnt9fQ.d3nUeeL_69QsrHL0ZWij612LHEQxD8EZg1rNoY3a4aI"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("object"), is(notNullValue())); assertThat(jwt.getClaim("object").isNull(), is(false)); @@ -262,7 +262,7 @@ public void shouldGetCustomMapClaim() throws Exception { @Test public void shouldGetAvailableClaims() throws Exception { - DecodedJWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTIzNDU2Nzg5MCIsIm5iZiI6IjEyMzQ1Njc4OTAiLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.TX9Ct4feGp9YyeGK9Zl91tO0YBOrguJ4As9jeqgHdZQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTIzNDU2Nzg5MCIsIm5iZiI6IjEyMzQ1Njc4OTAiLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.TX9Ct4feGp9YyeGK9Zl91tO0YBOrguJ4As9jeqgHdZQ"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaims(), is(notNullValue())); assertThat(jwt.getClaims(), is(instanceOf(Map.class))); @@ -281,7 +281,7 @@ public void shouldGetAvailableClaims() throws Exception { private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) { String header = Base64.encodeBase64URLSafeString(jsonHeader.getBytes(StandardCharsets.UTF_8)); String body = Base64.encodeBase64URLSafeString(jsonPayload.getBytes(StandardCharsets.UTF_8)); - return JWTDecoder.decode(String.format("%s.%s.%s", header, body, signature)); + return JWT.decode(String.format("%s.%s.%s", header, body, signature)); } } \ No newline at end of file From cf123a6b020d8e1a4b0f15e38a0d528e0e0c5a19 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 20 Apr 2017 12:41:27 -0300 Subject: [PATCH 049/355] improve token parts handling. Remove Signature interface. --- .../main/java/com/auth0/jwt/JWTDecoder.java | 29 +++++---- .../main/java/com/auth0/jwt/JWTVerifier.java | 65 +++++++------------ .../com/auth0/jwt/interfaces/DecodedJWT.java | 26 +++++++- .../java/com/auth0/jwt/interfaces/Header.java | 1 - 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 1f1eed7b..7921c128 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -19,18 +19,12 @@ @SuppressWarnings("WeakerAccess") final class JWTDecoder implements DecodedJWT { - private final String token; - private Header header; - private Payload payload; - private String signature; + private final String[] parts; + private final Header header; + private final Payload payload; JWTDecoder(String jwt) throws JWTDecodeException { - this.token = jwt; - parseToken(jwt); - } - - private void parseToken(String token) throws JWTDecodeException { - final String[] parts = TokenUtils.splitToken(token); + parts = TokenUtils.splitToken(jwt); final JWTParser converter = new JWTParser(); String headerJson; String payloadJson; @@ -42,7 +36,6 @@ private void parseToken(String token) throws JWTDecodeException { } header = converter.parseHeader(headerJson); payload = converter.parsePayload(payloadJson); - signature = parts[2]; } @Override @@ -115,13 +108,23 @@ public Map getClaims() { return payload.getClaims(); } + @Override + public String getHeader() { + return parts[0]; + } + + @Override + public String getPayload() { + return parts[1]; + } + @Override public String getSignature() { - return signature; + return parts[2]; } @Override public String getToken() { - return token; + return String.format("%s.%s.%s", parts[0], parts[1], parts[2]); } } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index e69c305e..f6f815c1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,19 +1,13 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.exceptions.AlgorithmMismatchException; -import com.auth0.jwt.exceptions.InvalidClaimException; -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; -import org.apache.commons.codec.binary.Base64; -import java.nio.charset.StandardCharsets; import java.util.*; /** @@ -352,19 +346,13 @@ private void requireClaim(String name, Object value) { * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. */ public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWTDecoder.decode(token); + DecodedJWT jwt = JWT.decode(token); verifyAlgorithm(jwt, algorithm); - verifySignature(TokenUtils.splitToken(token)); + algorithm.verify(jwt); verifyClaims(jwt, claims); return jwt; } - private void verifySignature(String[] parts) throws SignatureVerificationException { - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = Base64.decodeBase64(parts[2]); - algorithm.verify(content, signature); - } - private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); @@ -435,31 +423,28 @@ private void assertValidStringClaim(String claimName, String value, String expec } private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); - today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate - // millis - if (shouldBeFuture) { - assertDateIsFuture(date, leeway, today); - } else { - assertDateIsPast(date, leeway, today); - } - } - - private void assertDateIsFuture(Date date, long leeway, Date today) { - - today.setTime(today.getTime() - leeway * 1000); - if (date != null && today.after(date)) { - throw new TokenExpiredException(String.format("The Token has expired on %s.", date)); - } - } - - private void assertDateIsPast(Date date, long leeway, Date today) { - today.setTime(today.getTime() + leeway * 1000); - if(date!=null && today.before(date)) { - throw new InvalidClaimException(String.format("The Token can't be used before %s.", date)); - } - - } + Date today = clock.getToday(); + today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate millis + if (shouldBeFuture) { + assertDateIsFuture(date, leeway, today); + } else { + assertDateIsPast(date, leeway, today); + } + } + + private void assertDateIsFuture(Date date, long leeway, Date today) { + today.setTime(today.getTime() - leeway * 1000); + if (date != null && today.after(date)) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", date)); + } + } + + private void assertDateIsPast(Date date, long leeway, Date today) { + today.setTime(today.getTime() + leeway * 1000); + if (date != null && today.before(date)) { + throw new InvalidClaimException(String.format("The Token can't be used before %s.", date)); + } + } private void assertValidAudienceClaim(List audience, List value) { if (audience == null || !audience.containsAll(value)) { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java b/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java index 56af6ff7..04307b28 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/DecodedJWT.java @@ -3,11 +3,35 @@ /** * Class that represents a Json Web Token that was decoded from it's string representation. */ -public interface DecodedJWT extends Payload, Header, Signature { +public interface DecodedJWT extends Payload, Header { /** * Getter for the String Token used to create this JWT instance. * * @return the String Token. */ String getToken(); + + /** + * Getter for the Header contained in the JWT as a Base64 encoded String. + * This represents the first part of the token. + * + * @return the Header of the JWT. + */ + String getHeader(); + + /** + * Getter for the Payload contained in the JWT as a Base64 encoded String. + * This represents the second part of the token. + * + * @return the Payload of the JWT. + */ + String getPayload(); + + /** + * Getter for the Signature contained in the JWT as a Base64 encoded String. + * This represents the third part of the token. + * + * @return the Signature of the JWT. + */ + String getSignature(); } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java index 2af99788..0a83d1ce 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java @@ -26,7 +26,6 @@ public interface Header { */ String getContentType(); - /** * Get the value of the "kid" claim, or null if it's not available. * From 0001be066d0e6547792480de0dac099a649a6b02 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 20 Apr 2017 16:07:55 -0300 Subject: [PATCH 050/355] refactor KeyProvider to use KeyId --- .../main/java/com/auth0/jwt/JWTCreator.java | 8 +- .../com/auth0/jwt/algorithms/Algorithm.java | 17 +- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 43 +-- .../auth0/jwt/algorithms/HMACAlgorithm.java | 14 +- .../auth0/jwt/algorithms/NoneAlgorithm.java | 5 +- .../auth0/jwt/algorithms/RSAAlgorithm.java | 44 +-- .../com/auth0/jwt/interfaces/KeyProvider.java | 10 +- .../com/auth0/jwt/interfaces/Signature.java | 14 - .../java/com/auth0/jwt/JWTCreatorTest.java | 76 +++++ .../java/com/auth0/jwt/JWTDecoderTest.java | 21 +- .../auth0/jwt/algorithms/AlgorithmTest.java | 108 ++++--- .../auth0/jwt/algorithms/AlgorithmUtils.java | 18 -- .../jwt/algorithms/ECDSAAlgorithmTest.java | 288 +++++++++++++++--- .../jwt/algorithms/HMACAlgorithmTest.java | 104 ++++--- .../jwt/algorithms/NoneAlgorithmTest.java | 23 +- .../jwt/algorithms/RSAAlgorithmTest.java | 285 ++++++++++++++--- 16 files changed, 831 insertions(+), 247 deletions(-) delete mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/Signature.java delete mode 100644 lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmUtils.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index fa93be95..10c257f8 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -303,6 +303,10 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea } headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); headerClaims.put(PublicClaims.TYPE, "JWT"); + String signingKeyId = algorithm.getSigningKeyId(); + if (!headerClaims.containsKey(PublicClaims.KEY_ID) && signingKeyId != null) { + withKeyId(signingKeyId); + } return new JWTCreator(algorithm, headerClaims, payloadClaims).sign(); } @@ -322,8 +326,8 @@ private void addClaim(String name, Object value) { } private String sign() throws SignatureGenerationException { - String header = Base64.encodeBase64URLSafeString((headerJson.getBytes(StandardCharsets.UTF_8))); - String payload = Base64.encodeBase64URLSafeString((payloadJson.getBytes(StandardCharsets.UTF_8))); + String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8)); + String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8)); String content = String.format("%s.%s", header, payload); byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8)); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index eb484478..c34702d0 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; @@ -324,6 +325,15 @@ protected Algorithm(String name, String description) { this.description = description; } + /** + * Getter for the Id of the Private Key used to sign the tokens. This is usually specified as the `kid` claim in the Header. + * + * @return the Key Id that identifies the Signing Key or null if it's not specified. + */ + public String getSigningKeyId() { + return null; + } + /** * Getter for the name of this Algorithm, as defined in the JWT Standard. i.e. "HS256" * @@ -348,13 +358,12 @@ public String toString() { } /** - * Verify the given content using this Algorithm instance. + * Verify the given token using this Algorithm instance. * - * @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature. - * @param signatureBytes an array of bytes representing the base64 encoded signature to compare the content against. + * @param jwt the already decoded JWT that it's going to be verified. * @throws SignatureVerificationException if the Token's Signature is invalid, meaning that it doesn't match the signatureBytes, or if the Key is invalid. */ - public abstract void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException; + public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException; /** * Sign the given content using this Algorithm instance. 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 2b43c7e6..714cc6c1 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -2,8 +2,11 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECKeyProvider; +import org.apache.commons.codec.binary.Base64; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -22,9 +25,6 @@ class ECDSAAlgorithm extends Algorithm { if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); } - if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) { - throw new IllegalArgumentException("Both provided Keys cannot be null."); - } this.keyProvider = keyProvider; this.crypto = crypto; this.ecNumberSize = ecNumberSize; @@ -34,24 +34,20 @@ class ECDSAAlgorithm extends Algorithm { this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } - ECPublicKey getPublicKey() { - return keyProvider.getPublicKey(); - } - - ECPrivateKey getPrivateKey() { - return keyProvider.getPrivateKey(); - } - @Override - public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { + public void verify(DecodedJWT jwt) throws SignatureVerificationException { + byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); + try { - if (keyProvider.getPublicKey() == null) { + ECPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId()); + if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } if (!isDERSignature(signatureBytes)) { signatureBytes = JOSEToDER(signatureBytes); } - boolean valid = crypto.verifySignatureFor(getDescription(), keyProvider.getPublicKey(), contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); @@ -64,15 +60,20 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (keyProvider.getPrivateKey() == null) { + ECPrivateKey privateKey = keyProvider.getPrivateKey(); + if (privateKey == null) { throw new IllegalStateException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes); + return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } + @Override + public String getSigningKeyId() { + return keyProvider.getSigningKeyId(); + } private boolean isDERSignature(byte[] signature) { // DER Structure: http://crypto.stackexchange.com/a/1797 @@ -138,9 +139,12 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) { //Visible for testing static ECKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) { + if (publicKey == null && privateKey == null) { + throw new IllegalArgumentException("Both provided Keys cannot be null."); + } return new ECKeyProvider() { @Override - public ECPublicKey getPublicKey() { + public ECPublicKey getPublicKey(String keyId) { return publicKey; } @@ -148,6 +152,11 @@ public ECPublicKey getPublicKey() { public ECPrivateKey getPrivateKey() { return privateKey; } + + @Override + public String getSigningKeyId() { + return null; + } }; } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 63f2d580..afe1b30e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -2,9 +2,12 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; import org.apache.commons.codec.CharEncoding; +import org.apache.commons.codec.binary.Base64; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -13,6 +16,7 @@ class HMACAlgorithm extends Algorithm { private final CryptoHelper crypto; private final byte[] secret; + //Visible for testing HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) throws IllegalArgumentException { super(id, algorithm); if (secretBytes == null) { @@ -30,6 +34,7 @@ class HMACAlgorithm extends Algorithm { this(new CryptoHelper(), id, algorithm, getSecretBytes(secret)); } + //Visible for testing static byte[] getSecretBytes(String secret) throws IllegalArgumentException, UnsupportedEncodingException { if (secret == null) { throw new IllegalArgumentException("The Secret cannot be null"); @@ -37,12 +42,11 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException, Uns return secret.getBytes(CharEncoding.UTF_8); } - byte[] getSecret() { - return secret; - } - @Override - public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { + public void verify(DecodedJWT jwt) throws SignatureVerificationException { + byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); + try { boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes); if (!valid) { diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index 97b55edc..136cf886 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -2,6 +2,8 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.apache.commons.codec.binary.Base64; class NoneAlgorithm extends Algorithm { @@ -10,7 +12,8 @@ class NoneAlgorithm extends Algorithm { } @Override - public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { + public void verify(DecodedJWT jwt) throws SignatureVerificationException { + byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); if (signatureBytes.length > 0) { throw new SignatureVerificationException(this); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index a5d7d3f3..5083fbc7 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -2,8 +2,11 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; +import org.apache.commons.codec.binary.Base64; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -21,9 +24,6 @@ class RSAAlgorithm extends Algorithm { if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); } - if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) { - throw new IllegalArgumentException("Both provided Keys cannot be null."); - } this.keyProvider = keyProvider; this.crypto = crypto; } @@ -32,21 +32,17 @@ class RSAAlgorithm extends Algorithm { this(new CryptoHelper(), id, algorithm, keyProvider); } - RSAPublicKey getPublicKey() { - return keyProvider.getPublicKey(); - } - - RSAPrivateKey getPrivateKey() { - return keyProvider.getPrivateKey(); - } - @Override - public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException { + public void verify(DecodedJWT jwt) throws SignatureVerificationException { + byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); + try { - if (keyProvider.getPublicKey() == null) { + RSAPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId()); + if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), keyProvider.getPublicKey(), contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } @@ -58,20 +54,29 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { - if (keyProvider.getPrivateKey() == null) { + RSAPrivateKey privateKey = keyProvider.getPrivateKey(); + if (privateKey == null) { throw new IllegalStateException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes); + return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } } + @Override + public String getSigningKeyId() { + return keyProvider.getSigningKeyId(); + } + //Visible for testing static RSAKeyProvider providerForKeys(final RSAPublicKey publicKey, final RSAPrivateKey privateKey) { + if (publicKey == null && privateKey == null) { + throw new IllegalArgumentException("Both provided Keys cannot be null."); + } return new RSAKeyProvider() { @Override - public RSAPublicKey getPublicKey() { + public RSAPublicKey getPublicKey(String keyId) { return publicKey; } @@ -79,6 +84,11 @@ public RSAPublicKey getPublicKey() { public RSAPrivateKey getPrivateKey() { return privateKey; } + + @Override + public String getSigningKeyId() { + return null; + } }; } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index 10f419fe..c891e57d 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -14,9 +14,10 @@ interface KeyProvider { /** * Getter for the Public Key instance, used to verify the signature. * + * @param keyId the Key Id specified in the Token's Header or null if none is available. Provides a hint on which Public Key to use to verify the token's signature. * @return the Public Key instance */ - U getPublicKey(); + U getPublicKey(String keyId); /** * Getter for the Private Key instance, used to sign the content. @@ -24,4 +25,11 @@ interface KeyProvider { * @return the Private Key instance */ R getPrivateKey(); + + /** + * Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header if no other "Key Id" has been set already. + * + * @return the Key Id that identifies the Signing Key or null if it's not specified. + */ + String getSigningKeyId(); } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Signature.java b/lib/src/main/java/com/auth0/jwt/interfaces/Signature.java deleted file mode 100644 index ae190bf3..00000000 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Signature.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.auth0.jwt.interfaces; - -/** - * The Signature class represents the 3rd part of the JWT, where the Signature value is hold. - */ -public interface Signature { - - /** - * Getter for the Signature contained in the JWT as a Base64 encoded String. - * - * @return the Signature of the JWT. - */ - String getSignature(); -} diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index a4d5204c..355482b1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -1,12 +1,16 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.RSAKeyProvider; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.nio.charset.StandardCharsets; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -14,9 +18,15 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class JWTCreatorTest { + private static final String PRIVATE_KEY_FILE_RSA = "src/test/resources/rsa-private.pem"; + private static final String PRIVATE_KEY_FILE_EC_256 = "src/test/resources/ec256-key-private.pem"; + + @Rule public ExpectedException exception = ExpectedException.none(); @@ -55,6 +65,72 @@ public void shouldAddKeyId() throws Exception { assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed")); } + @Test + public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingRSAAlgorithms() throws Exception { + RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"); + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .sign(Algorithm.RSA256(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); + } + + @Test + public void shouldNotOverwriteKeyIdIfAlreadySetUsingRSAAlgorithms() throws Exception { + RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"); + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .withKeyId("real-key-id") + .sign(Algorithm.RSA256(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id")); + } + + @Test + public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingECDSAAlgorithms() throws Exception { + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .sign(Algorithm.ECDSA256(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); + } + + @Test + public void shouldNotOverwriteKeyIdIfAlreadySetUsingECDSAAlgorithms() throws Exception { + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .withKeyId("real-key-id") + .sign(Algorithm.ECDSA256(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id")); + } + @Test public void shouldAddIssuer() throws Exception { String signed = JWTCreator.init() diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 755ff3f0..a482685e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -63,7 +63,8 @@ public void shouldThrowIfHeaderHasInvalidJSONFormat() throws Exception { customJWT(invalidJson, validJson, "signature"); } - // getToken + // Parts + @Test public void shouldGetStringToken() throws Exception { DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); @@ -72,13 +73,18 @@ public void shouldGetStringToken() throws Exception { assertThat(jwt.getToken(), is("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); } - // Parts + @Test + public void shouldGetHeader() throws Exception { + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + assertThat(jwt, is(notNullValue())); + assertThat(jwt.getHeader(), is("eyJhbGciOiJIUzI1NiJ9")); + } @Test - public void shouldGetAlgorithm() throws Exception { + public void shouldGetPayload() throws Exception { DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); assertThat(jwt, is(notNullValue())); - assertThat(jwt.getAlgorithm(), is("HS256")); + assertThat(jwt.getPayload(), is("e30")); } @Test @@ -174,6 +180,13 @@ public void shouldGetType() throws Exception { assertThat(jwt.getType(), is("JWS")); } + @Test + public void shouldGetAlgorithm() throws Exception { + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + assertThat(jwt, is(notNullValue())); + assertThat(jwt.getAlgorithm(), is("HS256")); + } + //Private PublicClaims @Test diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index 7fa463be..e66bf482 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -214,7 +214,6 @@ public void shouldCreateHMAC256AlgorithmWithBytes() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA256")); assertThat(algorithm.getName(), is("HS256")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -225,7 +224,6 @@ public void shouldCreateHMAC384AlgorithmWithBytes() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA384")); assertThat(algorithm.getName(), is("HS384")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -236,7 +234,6 @@ public void shouldCreateHMAC512AlgorithmWithBytes() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA512")); assertThat(algorithm.getName(), is("HS512")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -247,7 +244,6 @@ public void shouldCreateHMAC256AlgorithmWithString() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA256")); assertThat(algorithm.getName(), is("HS256")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -258,7 +254,6 @@ public void shouldCreateHMAC384AlgorithmWithString() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA384")); assertThat(algorithm.getName(), is("HS384")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -269,7 +264,6 @@ public void shouldCreateHMAC512AlgorithmWithString() throws Exception { assertThat(algorithm, is(instanceOf(HMACAlgorithm.class))); assertThat(algorithm.getDescription(), is("HmacSHA512")); assertThat(algorithm.getName(), is("HS512")); - assertThat(((HMACAlgorithm) algorithm).getSecret(), is("secret".getBytes(StandardCharsets.UTF_8))); } @Test @@ -281,7 +275,6 @@ public void shouldCreateRSA256AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withRSA")); assertThat(algorithm.getName(), is("RS256")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -293,11 +286,10 @@ public void shouldCreateRSA256AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withRSA")); assertThat(algorithm.getName(), is("RS256")); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateRSA256Algorithm() throws Exception { + public void shouldCreateRSA256AlgorithmWithBothKeys() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); @@ -306,8 +298,17 @@ public void shouldCreateRSA256Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withRSA")); assertThat(algorithm.getName(), is("RS256")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateRSA256AlgorithmWithProvider() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + Algorithm algorithm = Algorithm.RSA256(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withRSA")); + assertThat(algorithm.getName(), is("RS256")); } @Test @@ -319,7 +320,6 @@ public void shouldCreateRSA384AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withRSA")); assertThat(algorithm.getName(), is("RS384")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -331,11 +331,10 @@ public void shouldCreateRSA384AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withRSA")); assertThat(algorithm.getName(), is("RS384")); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateRSA384Algorithm() throws Exception { + public void shouldCreateRSA384AlgorithmWithBothKeys() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); Algorithm algorithm = Algorithm.RSA384(publicKey, privateKey); @@ -344,8 +343,17 @@ public void shouldCreateRSA384Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withRSA")); assertThat(algorithm.getName(), is("RS384")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateRSA384AlgorithmWithProvider() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + Algorithm algorithm = Algorithm.RSA384(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withRSA")); + assertThat(algorithm.getName(), is("RS384")); } @Test @@ -357,7 +365,6 @@ public void shouldCreateRSA512AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withRSA")); assertThat(algorithm.getName(), is("RS512")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -369,11 +376,10 @@ public void shouldCreateRSA512AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withRSA")); assertThat(algorithm.getName(), is("RS512")); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateRSA512Algorithm() throws Exception { + public void shouldCreateRSA512AlgorithmWithBothKeys() throws Exception { RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); Algorithm algorithm = Algorithm.RSA512(publicKey, privateKey); @@ -382,8 +388,17 @@ public void shouldCreateRSA512Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withRSA")); assertThat(algorithm.getName(), is("RS512")); - assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((RSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateRSA512AlgorithmWithProvider() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + Algorithm algorithm = Algorithm.RSA512(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(RSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withRSA")); + assertThat(algorithm.getName(), is("RS512")); } @Test @@ -395,7 +410,6 @@ public void shouldCreateECDSA256AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -407,11 +421,10 @@ public void shouldCreateECDSA256AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateECDSA256Algorithm() throws Exception { + public void shouldCreateECDSA256AlgorithmWithBothKeys() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); Algorithm algorithm = Algorithm.ECDSA256(publicKey, privateKey); @@ -420,8 +433,17 @@ public void shouldCreateECDSA256Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA256AlgorithmWithProvider() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + Algorithm algorithm = Algorithm.ECDSA256(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256")); } @Test @@ -433,7 +455,6 @@ public void shouldCreateECDSA384AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withECDSA")); assertThat(algorithm.getName(), is("ES384")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -445,11 +466,10 @@ public void shouldCreateECDSA384AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withECDSA")); assertThat(algorithm.getName(), is("ES384")); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateECDSA384Algorithm() throws Exception { + public void shouldCreateECDSA384AlgorithmWithBothKeys() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); Algorithm algorithm = Algorithm.ECDSA384(publicKey, privateKey); @@ -458,8 +478,17 @@ public void shouldCreateECDSA384Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA384withECDSA")); assertThat(algorithm.getName(), is("ES384")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA384AlgorithmWithProvider() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + Algorithm algorithm = Algorithm.ECDSA384(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA384withECDSA")); + assertThat(algorithm.getName(), is("ES384")); } @Test @@ -471,7 +500,6 @@ public void shouldCreateECDSA512AlgorithmWithPublicKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withECDSA")); assertThat(algorithm.getName(), is("ES512")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key)); } @Test @@ -483,11 +511,10 @@ public void shouldCreateECDSA512AlgorithmWithPrivateKey() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withECDSA")); assertThat(algorithm.getName(), is("ES512")); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(key)); } @Test - public void shouldCreateECDSA512Algorithm() throws Exception { + public void shouldCreateECDSA512AlgorithmWithBothKeys() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); Algorithm algorithm = Algorithm.ECDSA512(publicKey, privateKey); @@ -496,8 +523,17 @@ public void shouldCreateECDSA512Algorithm() throws Exception { assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); assertThat(algorithm.getDescription(), is("SHA512withECDSA")); assertThat(algorithm.getName(), is("ES512")); - assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(publicKey)); - assertThat(((ECDSAAlgorithm) algorithm).getPrivateKey(), is(privateKey)); + } + + @Test + public void shouldCreateECDSA512AlgorithmWithProvider() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + Algorithm algorithm = Algorithm.ECDSA512(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA512withECDSA")); + assertThat(algorithm.getName(), is("ES512")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmUtils.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmUtils.java deleted file mode 100644 index c8eaccfa..00000000 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmUtils.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.auth0.jwt.algorithms; - -import org.apache.commons.codec.binary.Base64; - -import java.nio.charset.StandardCharsets; - -public class AlgorithmUtils { - - public static void verify(Algorithm algorithm, String jwt) { - String[] parts = jwt.split("\\."); - byte[] content = String.format("%s.%s", parts[0], parts[1]).getBytes(StandardCharsets.UTF_8); - byte[] signature = new byte[0]; - if (parts.length == 3) { - signature = Base64.decodeBase64(parts[2]); - } - algorithm.verify(content, signature); - } -} 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 b169b554..1a1fee01 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1,5 +1,6 @@ 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.ECKeyProvider; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@SuppressWarnings("deprecation") public class ECDSAAlgorithmTest { private static final String PRIVATE_KEY_FILE_256 = "src/test/resources/ec256-key-private.pem"; @@ -51,7 +53,7 @@ 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); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -59,21 +61,44 @@ public void shouldPassECDSA256VerificationWithDERSignature() throws Exception { String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); Algorithm algorithm = Algorithm.ECDSA256(key); - AlgorithmUtils.verify(algorithm, jwt); + 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassECDSA256VerificationWithDERSignatureWithBothKeys() throws Exception { 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA256VerificationWithProvidedPublicKey() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + when(provider.getPublicKey("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."))); + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPublicKey("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.D_oU4CB0ZEsxHOjcWnmS3ZJvlTzm6WcGFx-HASxnvcB2Xu2WjI-axqXH9xKq45aPBDs330JpRhJmqBSc2K8MXQ"; + Algorithm algorithm = Algorithm.ECDSA256(provider); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -82,7 +107,7 @@ public void shouldFailECDSA256VerificationWithInvalidPublicKey() throws Exceptio 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -93,7 +118,7 @@ public void shouldFailECDSA256VerificationWhenUsingPrivateKey() throws Exception 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -108,7 +133,7 @@ public void shouldFailECDSA256VerificationOnInvalidSignatureLength() throws Exce String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -121,7 +146,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Except String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -135,7 +160,7 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -143,7 +168,7 @@ 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); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -151,21 +176,44 @@ public void shouldPassECDSA384VerificationWithDERSignature() throws Exception { 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); - AlgorithmUtils.verify(algorithm, jwt); + 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassECDSA384VerificationWithDERSignatureWithBothKeys() throws Exception { 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA384VerificationWithProvidedPublicKey() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + when(provider.getPublicKey("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."))); + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPublicKey("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJFUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.9kjGuFTPx3ylfpqL0eY9H7TGmPepjQOBKI8UPoEvby6N7dDLF5HxLohosNxxFymNT7LzpeSgOPAB0wJEwG2Nl2ukgdUOpZOf492wog_i5ZcZmAykd3g1QH7onrzd69GU"; + Algorithm algorithm = Algorithm.ECDSA384(provider); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -174,7 +222,7 @@ public void shouldFailECDSA384VerificationWithInvalidPublicKey() throws Exceptio 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -185,7 +233,7 @@ public void shouldFailECDSA384VerificationWhenUsingPrivateKey() throws Exception 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -200,7 +248,7 @@ public void shouldFailECDSA384VerificationOnInvalidSignatureLength() throws Exce String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -213,7 +261,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Except String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -227,7 +275,7 @@ public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Excepti String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -235,7 +283,7 @@ 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); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -243,21 +291,44 @@ public void shouldPassECDSA512VerificationWithDERSignature() throws Exception { String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); Algorithm algorithm = Algorithm.ECDSA512(key); - AlgorithmUtils.verify(algorithm, jwt); + 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassECDSA512VerificationWithDERSignatureWithBothKeys() throws Exception { 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA512VerificationWithProvidedPublicKey() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + when(provider.getPublicKey("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."))); + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPublicKey("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 @@ -266,7 +337,7 @@ public void shouldFailECDSA512VerificationWithInvalidPublicKey() throws Exceptio 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -277,7 +348,7 @@ public void shouldFailECDSA512VerificationWhenUsingPrivateKey() throws Exception 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")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -292,7 +363,7 @@ public void shouldFailECDSA512VerificationOnInvalidSignatureLength() throws Exce String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -305,7 +376,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Except String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -319,7 +390,7 @@ public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Excepti String signature = Base64.encodeBase64URLSafeString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -338,7 +409,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E ECPrivateKey privateKey = mock(ECPrivateKey.class); ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, provider); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -356,7 +427,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce ECKeyProvider 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"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -374,7 +445,7 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { ECKeyProvider 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"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -392,7 +463,7 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception ECKeyProvider 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"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } //Sign @@ -405,21 +476,58 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception 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")); - byte[] contentBytes = String.format("%s.%s", ES256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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(contentBytes, signatureBytes); + 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")); - byte[] contentBytes = String.format("%s.%s", ES256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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 { + ECKeyProvider provider = mock(ECKeyProvider.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.getPublicKey(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(contentBytes, signatureBytes); + 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."))); + + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA256(provider); + algorithm.sign(new byte[0]); } @Test @@ -437,21 +545,58 @@ public void shouldFailOnECDSA256SigningWhenUsingPublicKey() throws Exception { 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")); - byte[] contentBytes = String.format("%s.%s", ES384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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(contentBytes, signatureBytes); + 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")); - byte[] contentBytes = String.format("%s.%s", ES384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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(contentBytes, signatureBytes); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.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.getPublicKey(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."))); + + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA384(provider); + algorithm.sign(new byte[0]); } @Test @@ -469,21 +614,59 @@ public void shouldFailOnECDSA384SigningWhenUsingPublicKey() throws Exception { 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")); - byte[] contentBytes = String.format("%s.%s", ES512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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(contentBytes, signatureBytes); + 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")); - byte[] contentBytes = String.format("%s.%s", ES512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + 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(contentBytes, signatureBytes); + algorithm.verify(JWT.decode(jwt)); + } + + + @Test + public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { + ECKeyProvider provider = mock(ECKeyProvider.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.getPublicKey(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."))); + + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA512(provider); + algorithm.sign(new byte[0]); } @Test @@ -547,4 +730,23 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { 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); + ECKeyProvider 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 { + ECKeyProvider provider = mock(ECKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("keyId"); + Algorithm algorithm = new ECDSAAlgorithm("some-alg", "some-algorithm", 32, provider); + + assertThat(algorithm.getSigningKeyId(), is("keyId")); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 7e25c66f..2d5fd301 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -1,7 +1,9 @@ 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.DecodedJWT; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -39,8 +41,9 @@ public void shouldPassHMAC256Verification() throws Exception { String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; Algorithm algorithmString = Algorithm.HMAC256("secret"); Algorithm algorithmBytes = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithmString, jwt); - AlgorithmUtils.verify(algorithmBytes, jwt); + DecodedJWT decoded = JWT.decode(jwt); + algorithmString.verify(decoded); + algorithmBytes.verify(decoded); } @Test @@ -49,7 +52,7 @@ public void shouldFailHMAC256VerificationWithInvalidSecretString() throws Except exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256"); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; Algorithm algorithm = Algorithm.HMAC256("not_real_secret"); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -58,7 +61,7 @@ public void shouldFailHMAC256VerificationWithInvalidSecretBytes() throws Excepti exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256"); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; Algorithm algorithm = Algorithm.HMAC256("not_real_secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -66,8 +69,9 @@ public void shouldPassHMAC384Verification() throws Exception { String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; Algorithm algorithmString = Algorithm.HMAC384("secret"); Algorithm algorithmBytes = Algorithm.HMAC384("secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithmString, jwt); - AlgorithmUtils.verify(algorithmBytes, jwt); + DecodedJWT decoded = JWT.decode(jwt); + algorithmString.verify(decoded); + algorithmBytes.verify(decoded); } @Test @@ -76,7 +80,7 @@ public void shouldFailHMAC384VerificationWithInvalidSecretString() throws Except exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA384"); String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; Algorithm algorithm = Algorithm.HMAC384("not_real_secret"); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -85,7 +89,7 @@ public void shouldFailHMAC384VerificationWithInvalidSecretBytes() throws Excepti exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA384"); String jwt = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; Algorithm algorithm = Algorithm.HMAC384("not_real_secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -93,8 +97,9 @@ public void shouldPassHMAC512Verification() throws Exception { String jwt = "eyJhbGciOiJIUzUxMiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.VUo2Z9SWDV-XcOc_Hr6Lff3vl7L9e5Vb8ThXpmGDFjHxe3Dr1ZBmUChYF-xVA7cAdX1P_D4ZCUcsv3IefpVaJw"; Algorithm algorithmString = Algorithm.HMAC512("secret"); Algorithm algorithmBytes = Algorithm.HMAC512("secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithmString, jwt); - AlgorithmUtils.verify(algorithmBytes, jwt); + DecodedJWT decoded = JWT.decode(jwt); + algorithmString.verify(decoded); + algorithmBytes.verify(decoded); } @Test @@ -103,7 +108,7 @@ public void shouldFailHMAC512VerificationWithInvalidSecretString() throws Except exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512"); String jwt = "eyJhbGciOiJIUzUxMiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.VUo2Z9SWDV-XcOc_Hr6Lff3vl7L9e5Vb8ThXpmGDFjHxe3Dr1ZBmUChYF-xVA7cAdX1P_D4ZCUcsv3IefpVaJw"; Algorithm algorithm = Algorithm.HMAC512("not_real_secret"); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -112,7 +117,7 @@ public void shouldFailHMAC512VerificationWithInvalidSecretBytes() throws Excepti exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512"); String jwt = "eyJhbGciOiJIUzUxMiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.VUo2Z9SWDV-XcOc_Hr6Lff3vl7L9e5Vb8ThXpmGDFjHxe3Dr1ZBmUChYF-xVA7cAdX1P_D4ZCUcsv3IefpVaJw"; Algorithm algorithm = Algorithm.HMAC512("not_real_secret".getBytes(StandardCharsets.UTF_8)); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -127,7 +132,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -142,7 +147,7 @@ public void shouldThrowOnVerifyWhenTheSecretIsInvalid() throws Exception { Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } // Sign @@ -155,79 +160,97 @@ public void shouldThrowOnVerifyWhenTheSecretIsInvalid() throws Exception { @Test public void shouldDoHMAC256SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); - byte[] contentBytes = String.format("%s.%s", HS256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldDoHMAC384SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC384("secret".getBytes(StandardCharsets.UTF_8)); - byte[] contentBytes = String.format("%s.%s", HS384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "4-y2Gxz_foN0jAOFimmBPF7DWxf4AsjM20zxNkHg8Zah5Q64G42P9GfjmUp4Hldt"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldDoHMAC512SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC512("secret".getBytes(StandardCharsets.UTF_8)); - byte[] contentBytes = String.format("%s.%s", HS512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "OXWyxmf-VcVo8viOiTFfLaEy6mrQqLEos5R82Xsx8mtFxQadJAQ1aVniIWN8qT2GNE_pMQPcdzk4x7Cqxsp1dw"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldDoHMAC256SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret"); - byte[] contentBytes = String.format("%s.%s", HS256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldDoHMAC384SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC384("secret"); - byte[] contentBytes = String.format("%s.%s", HS384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "4-y2Gxz_foN0jAOFimmBPF7DWxf4AsjM20zxNkHg8Zah5Q64G42P9GfjmUp4Hldt"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldDoHMAC512SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC512("secret"); - byte[] contentBytes = String.format("%s.%s", HS512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + + String jwtContent = String.format("%s.%s", HS512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "OXWyxmf-VcVo8viOiTFfLaEy6mrQqLEos5R82Xsx8mtFxQadJAQ1aVniIWN8qT2GNE_pMQPcdzk4x7Cqxsp1dw"; assertThat(signatureBytes, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -258,4 +281,9 @@ public void shouldThrowOnSignWhenTheSecretIsInvalid() throws Exception { algorithm.sign(new byte[0]); } + @Test + public void shouldReturnNullSigningKeyId() throws Exception { + assertThat(Algorithm.HMAC256("secret").getSigningKeyId(), is(nullValue())); + } + } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java index 18794640..f6e72b84 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java @@ -1,10 +1,16 @@ package com.auth0.jwt.algorithms; +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.SignatureVerificationException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + public class NoneAlgorithmTest { @Rule @@ -13,8 +19,17 @@ public class NoneAlgorithmTest { @Test public void shouldPassNoneVerification() throws Exception { Algorithm algorithm = Algorithm.none(); + String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9."; + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailNoneVerificationWhenTokenHasTwoParts() throws Exception { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The token was expected to have 3 parts, but got 2."); String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9"; - AlgorithmUtils.verify(algorithm, jwt); + Algorithm algorithm = Algorithm.none(); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -23,7 +38,11 @@ public void shouldFailNoneVerificationWhenSignatureIsPresent() throws Exception exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: none"); String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.Ox-WRXRaGAuWt2KfPvWiGcCrPqZtbp_4OnQzZXaTfss"; Algorithm algorithm = Algorithm.none(); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } + @Test + public void shouldReturnNullSigningKeyId() throws Exception { + assertThat(Algorithm.none().getSigningKeyId(), is(nullValue())); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index 951d83f0..939f7059 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -1,5 +1,6 @@ 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.RSAKeyProvider; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@SuppressWarnings("deprecation") public class RSAAlgorithmTest { private static final String PRIVATE_KEY_FILE = "src/test/resources/rsa-private.pem"; @@ -39,14 +41,37 @@ public class RSAAlgorithmTest { public void shouldPassRSA256Verification() throws Exception { String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassRSA256VerificationWithBothKeys() throws Exception { String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassRSA256VerificationWithProvidedPublicKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + String jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.jXrbue3xJmnzWH9kU-uGeCTtgbQEKbch8uHd4Z52t86ncNyepfusl_bsyLJIcxMwK7odRzKiSE9efV9JaRSEDODDBdMeCzODFx82uBM7e46T1NLVSmjYIM7Hcfh81ZeTIk-hITvgtL6hvTdeJWOCZAB0bs18qSVW5SvursRUhY38xnhuNI6HOHCtqp7etxWAu6670L53I3GtXsmi6bXIzv_0v1xZcAFg4HTvXxfhfj3oCqkSs2nC27mHxBmQtmZKWmXk5HzVUyPRwTUWx5wHPT_hCsGer-CMCAyGsmOg466y1KDqf7ogpMYojfVZGWBsyA39LO1oWZ4Ryomkn8t5Vg"; + Algorithm algorithm = Algorithm.RSA256(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailRSA256VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPublicKey("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.jXrbue3xJmnzWH9kU-uGeCTtgbQEKbch8uHd4Z52t86ncNyepfusl_bsyLJIcxMwK7odRzKiSE9efV9JaRSEDODDBdMeCzODFx82uBM7e46T1NLVSmjYIM7Hcfh81ZeTIk-hITvgtL6hvTdeJWOCZAB0bs18qSVW5SvursRUhY38xnhuNI6HOHCtqp7etxWAu6670L53I3GtXsmi6bXIzv_0v1xZcAFg4HTvXxfhfj3oCqkSs2nC27mHxBmQtmZKWmXk5HzVUyPRwTUWx5wHPT_hCsGer-CMCAyGsmOg466y1KDqf7ogpMYojfVZGWBsyA39LO1oWZ4Ryomkn8t5Vg"; + Algorithm algorithm = Algorithm.RSA256(provider); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -55,7 +80,7 @@ public void shouldFailRSA256VerificationWithInvalidPublicKey() throws Exception exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -66,21 +91,44 @@ public void shouldFailRSA256VerificationWhenUsingPrivateKey() throws Exception { exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; Algorithm algorithm = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassRSA384Verification() throws Exception { String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassRSA384VerificationWithBothKeys() throws Exception { String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassRSA384VerificationWithProvidedPublicKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + String jwt = "eyJhbGciOiJSUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.ITNTVCT7ercumZKHV4-BXGkJwwa7fyF3CnSfEvm09fDFSkaseDxNo_75WLDmK9WM8RMHTPvkpHcTKm4guYEbC_la7RzFIKpU72bppzQojggSmWWXt_6zq50QP2t5HFMebote1zxhp8ccEdSCX5pyY6J2sm9kJ__HKK32KxIVCTjVCz-bFBS60oG35aYEySdKsxuUdWbD5FQ9I16Ony2x0EPvmlL3GPiAPmgjSFp3LtcBIbCDaoonM7iuDRGIQiDN_n2FKKb1Bt4_38uWPtTkwRpNalt6l53Y3JDdzGI5fMrMo3RQnQlAJxUJKD0eL6dRAA645IVIIXucHwuhgGGIVw"; + Algorithm algorithm = Algorithm.RSA384(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailRSA384VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPublicKey("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJSUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.ITNTVCT7ercumZKHV4-BXGkJwwa7fyF3CnSfEvm09fDFSkaseDxNo_75WLDmK9WM8RMHTPvkpHcTKm4guYEbC_la7RzFIKpU72bppzQojggSmWWXt_6zq50QP2t5HFMebote1zxhp8ccEdSCX5pyY6J2sm9kJ__HKK32KxIVCTjVCz-bFBS60oG35aYEySdKsxuUdWbD5FQ9I16Ony2x0EPvmlL3GPiAPmgjSFp3LtcBIbCDaoonM7iuDRGIQiDN_n2FKKb1Bt4_38uWPtTkwRpNalt6l53Y3JDdzGI5fMrMo3RQnQlAJxUJKD0eL6dRAA645IVIIXucHwuhgGGIVw"; + Algorithm algorithm = Algorithm.RSA384(provider); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -89,7 +137,7 @@ public void shouldFailRSA384VerificationWithInvalidPublicKey() throws Exception exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withRSA"); String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -100,21 +148,44 @@ public void shouldFailRSA384VerificationWhenUsingPrivateKey() throws Exception { exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw"; Algorithm algorithm = Algorithm.RSA384((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassRSA512Verification() throws Exception { String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test public void shouldPassRSA512VerificationWithBothKeys() throws Exception { String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassRSA512VerificationWithProvidedPublicKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + String jwt = "eyJhbGciOiJSUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.GpHv85Q8tAU_6hNWsmO0GEpO1qz9lmK3NKeAcemysz9MGo4FXWn8xbD8NjCfzZ8EWphm65M0NArKSjpKHO5-gcNsQxLBVfSED1vzcoaZH_Vy5Rp1M76dGH7JghB_66KrpfyMxer_yRJb-KXesNvIroDGilLQF2ENG-IfLF5nBKlDiVHmPaqr3pm1q20fNLhegkSRca4BJ5VdIlT6kOqE_ykVyCBqzD_oXp3LKO_ARnxoeB9SegIW1fy_3tuxSTKYsCZiOfiyVEXXblAuY3pSLZnGvgeBRnfvmWXDWhP0vVUFtYJBF09eULvvUMVqWcrjUG9gDzzzT7veiY_fHd_x8g"; + Algorithm algorithm = Algorithm.RSA512(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailRSA512VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPublicKey("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJSUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.GpHv85Q8tAU_6hNWsmO0GEpO1qz9lmK3NKeAcemysz9MGo4FXWn8xbD8NjCfzZ8EWphm65M0NArKSjpKHO5-gcNsQxLBVfSED1vzcoaZH_Vy5Rp1M76dGH7JghB_66KrpfyMxer_yRJb-KXesNvIroDGilLQF2ENG-IfLF5nBKlDiVHmPaqr3pm1q20fNLhegkSRca4BJ5VdIlT6kOqE_ykVyCBqzD_oXp3LKO_ARnxoeB9SegIW1fy_3tuxSTKYsCZiOfiyVEXXblAuY3pSLZnGvgeBRnfvmWXDWhP0vVUFtYJBF09eULvvUMVqWcrjUG9gDzzzT7veiY_fHd_x8g"; + Algorithm algorithm = Algorithm.RSA512(provider); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -123,7 +194,7 @@ public void shouldFailRSA512VerificationWithInvalidPublicKey() throws Exception exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withRSA"); String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -134,7 +205,7 @@ public void shouldFailRSA512VerificationWhenUsingPrivateKey() throws Exception { exception.expectCause(hasMessage(is("The given Public Key is null."))); String jwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; Algorithm algorithm = Algorithm.RSA512((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -152,7 +223,7 @@ public void shouldThrowWhenMacAlgorithmDoesNotExists() throws Exception { RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -170,7 +241,7 @@ public void shouldThrowWhenThePublicKeyIsInvalid() throws Exception { RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @Test @@ -188,7 +259,7 @@ public void shouldThrowWhenTheSignatureIsNotPrepared() throws Exception { RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; - AlgorithmUtils.verify(algorithm, jwt); + algorithm.verify(JWT.decode(jwt)); } @@ -203,28 +274,63 @@ public void shouldDoRSA256Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithmSign.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "ZB-Tr0vLtnf8I9fhSdSjU6HZei5xLYZQ6nZqM5O6Va0W9PgAqgRT7ShI9CjeYulRXPHvVmSl5EQuYuXdBzM0-H_3p_Nsl6tSMy4EyX2kkhEm6T0HhvarTh8CG0PCjn5p6FP5ZxWwhLcmRN70ItP6Z5MMO4CcJh1JrNxR4Fi4xQgt-CK2aVDMFXd-Br5yQiLVx1CX83w28OD9wssW3Rdltl5e66vCef0Ql6Q5I5e5F0nqGYT989a9fkNgLIx2F8k_az5x07BY59FV2SZg59nSiY7TZNjP8ot11Ew7HKRfPXOdh9eKRUVdhcxzqDePhyzKabU8TG5FP0SiWH5qVPfAgw"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithmVerify.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithmVerify.verify(JWT.decode(jwt)); } @Test public void shouldDoRSA256SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS256Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "ZB-Tr0vLtnf8I9fhSdSjU6HZei5xLYZQ6nZqM5O6Va0W9PgAqgRT7ShI9CjeYulRXPHvVmSl5EQuYuXdBzM0-H_3p_Nsl6tSMy4EyX2kkhEm6T0HhvarTh8CG0PCjn5p6FP5ZxWwhLcmRN70ItP6Z5MMO4CcJh1JrNxR4Fi4xQgt-CK2aVDMFXd-Br5yQiLVx1CX83w28OD9wssW3Rdltl5e66vCef0Ql6Q5I5e5F0nqGYT989a9fkNgLIx2F8k_az5x07BY59FV2SZg59nSiY7TZNjP8ot11Ew7HKRfPXOdh9eKRUVdhcxzqDePhyzKabU8TG5FP0SiWH5qVPfAgw"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoRSA256SigningWithProvidedPrivateKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); + when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + Algorithm algorithm = Algorithm.RSA256(provider); + String jwtContent = String.format("%s.%s", RS256Header, 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 shouldFailOnRSA256SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.RSA256(provider); + algorithm.sign(new byte[0]); } @Test @@ -243,28 +349,63 @@ public void shouldDoRSA384Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA384((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithmSign.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "Jx1PaTBnjd_U56MNjifFcY7w9ImDbseg0y8Ijr2pSiA1_wzQb_wy9undaWfzR5YqdIAXvjS8AGuZUAzIoTG4KMgOgdVyYDz3l2jzj6wI-lgqfR5hTy1w1ruMUQ4_wobpdxAiJ4fEbg8Mi_GljOiCO-P1HilxKnpiOJZidR8MQGwTInsf71tOUkK4x5UsdmUueuZbaU-CL5kPnRfXmJj9CcdxZbD9oMlbo23dwkP5BNMrS2LwGGzc9C_-ypxrBIOVilG3WZxcSmuG86LjcZbnL6LBEfph5NmKBgQav147uipb_7umBEr1m2dYiB_9u606n3bcoo3rnsYYK_Xfi1GAEQ"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithmVerify.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithmVerify.verify(JWT.decode(jwt)); } @Test public void shouldDoRSA384SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA384((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS384Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "Jx1PaTBnjd_U56MNjifFcY7w9ImDbseg0y8Ijr2pSiA1_wzQb_wy9undaWfzR5YqdIAXvjS8AGuZUAzIoTG4KMgOgdVyYDz3l2jzj6wI-lgqfR5hTy1w1ruMUQ4_wobpdxAiJ4fEbg8Mi_GljOiCO-P1HilxKnpiOJZidR8MQGwTInsf71tOUkK4x5UsdmUueuZbaU-CL5kPnRfXmJj9CcdxZbD9oMlbo23dwkP5BNMrS2LwGGzc9C_-ypxrBIOVilG3WZxcSmuG86LjcZbnL6LBEfph5NmKBgQav147uipb_7umBEr1m2dYiB_9u606n3bcoo3rnsYYK_Xfi1GAEQ"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoRSA384SigningWithProvidedPrivateKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); + when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + Algorithm algorithm = Algorithm.RSA384(provider); + String jwtContent = String.format("%s.%s", RS384Header, 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 shouldFailOnRSA384SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA384withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.RSA384(provider); + algorithm.sign(new byte[0]); } @Test @@ -283,28 +424,63 @@ public void shouldDoRSA512Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA512((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithmSign.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "THIPVYzNZ1Yo_dm0k1UELqV0txs3SzyMopCyHcLXOOdgYXF4MlGvBqu0CFvgSga72Sp5LpuC1Oesj40v_QDsp2GTGDeWnvvcv_eo-b0LPSpmT2h1Ibrmu-z70u2rKf28pkN-AJiMFqi8sit2kMIp1bwIVOovPvMTQKGFmova4Xwb3G526y_PeLlflW1h69hQTIVcI67ACEkAC-byjDnnYIklA-B4GWcggEoFwQRTdRjAUpifA6HOlvnBbZZlUd6KXwEydxVS-eh1odwPjB2_sfbyy5HnLsvNdaniiZQwX7QbwLNT4F72LctYdHHM1QCrID6bgfgYp9Ij9CRX__XDEA"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithmVerify.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithmVerify.verify(JWT.decode(jwt)); } @Test public void shouldDoRSA512SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - byte[] contentBytes = String.format("%s.%s", RS512Header, auth0IssPayload).getBytes(StandardCharsets.UTF_8); + String jwtContent = String.format("%s.%s", RS512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = algorithm.sign(contentBytes); - String signature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); String expectedSignature = "THIPVYzNZ1Yo_dm0k1UELqV0txs3SzyMopCyHcLXOOdgYXF4MlGvBqu0CFvgSga72Sp5LpuC1Oesj40v_QDsp2GTGDeWnvvcv_eo-b0LPSpmT2h1Ibrmu-z70u2rKf28pkN-AJiMFqi8sit2kMIp1bwIVOovPvMTQKGFmova4Xwb3G526y_PeLlflW1h69hQTIVcI67ACEkAC-byjDnnYIklA-B4GWcggEoFwQRTdRjAUpifA6HOlvnBbZZlUd6KXwEydxVS-eh1odwPjB2_sfbyy5HnLsvNdaniiZQwX7QbwLNT4F72LctYdHHM1QCrID6bgfgYp9Ij9CRX__XDEA"; - assertThat(signature, is(notNullValue())); - assertThat(signature, is(expectedSignature)); - algorithm.verify(contentBytes, signatureBytes); + assertThat(signatureBytes, is(notNullValue())); + assertThat(jwtSignature, is(expectedSignature)); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoRSA512SigningWithProvidedPrivateKey() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); + when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + Algorithm algorithm = Algorithm.RSA512(provider); + String jwtContent = String.format("%s.%s", RS512Header, 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 shouldFailOnRSA512SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA512withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.RSA512(provider); + algorithm.sign(new byte[0]); } @Test @@ -368,4 +544,23 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); } + + @Test + public void shouldReturnNullSigningKeyIdIfCreatedWithDefaultProvider() throws Exception { + RSAPublicKey publicKey = mock(RSAPublicKey.class); + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new RSAAlgorithm("some-alg", "some-algorithm", provider); + + assertThat(algorithm.getSigningKeyId(), is(nullValue())); + } + + @Test + public void shouldReturnSigningKeyIdFromProvider() throws Exception { + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getSigningKeyId()).thenReturn("keyId"); + Algorithm algorithm = new RSAAlgorithm("some-alg", "some-algorithm", provider); + + assertThat(algorithm.getSigningKeyId(), is("keyId")); + } } \ No newline at end of file From b2eac8624c12bffb59bdbb612da40e81b7b934eb Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 20 Apr 2017 16:32:21 -0300 Subject: [PATCH 051/355] add specific exceptions to JWTVerifier#verify javadoc --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index f6f815c1..6f513c7e 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -343,7 +343,10 @@ private void requireClaim(String name, Object value) { * * @param token to verify. * @return a verified and decoded JWT. - * @throws JWTVerificationException if any of the required contents inside the JWT is invalid. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header it's not equal to the one defined in the {@link JWTVerifier}. + * @throws SignatureVerificationException if the signature is invalid. + * @throws TokenExpiredException if the token has expired. + * @throws InvalidClaimException if a claim contained a different value than the expected one. */ public DecodedJWT verify(String token) throws JWTVerificationException { DecodedJWT jwt = JWT.decode(token); @@ -359,7 +362,7 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws } } - private void verifyClaims(DecodedJWT jwt, Map claims) { + private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { switch (entry.getKey()) { case PublicClaims.AUDIENCE: From c76d07313d429eeb52203f02d6d8a2d019ca63ba Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 20 Apr 2017 17:06:41 -0300 Subject: [PATCH 052/355] add KeyProvider usage to the readme --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index 0a9fb466..28536ede 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,57 @@ The library implements JWT Verification and Signing using the following algorith ## Usage +### Pick the Algorithm + +The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. + +#### Using static secrets or keys: + +```java +//HMAC +Algorithm algorithmHS = Algorithm.HMAC256("secret"); + +//RSA +RSAPublicKey publicKey = //Get the key instance +RSAPrivateKey privateKey = //Get the key instance +Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); +``` + +#### Using a KeyProvider: + +By using a `KeyProvider` the library delegates the decision of which key to use in each case to the user. For the verification process, this means that the provider will be asked for a `PublicKey` with a given **Key Id** value. Your provider implementation should have the logic to fetch the right key, for example by parsing a JWKS file from a public domain like [auth0/jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) does. For the signing process, this means that the provider will be asked for a `PrivateKey` and it's associated **Key Id**, so it can set it in the Token's header for future verification in the same way. Check the [IETF draft](https://tools.ietf.org/html/rfc7517) for more information on how to implement this. + +The following snippet uses example classes showing how this would work: + + +```java +final MyOwnJwkProvider jwkProvider = new MyOwnJwkProvider("{JWKS_FILE_HOST}"); +final RSAPrivateKey signingKey = //Get the key instance +final String signingKeyId = //Create an Id for the above key + +RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKey(String keyId) { + //Value might be null if it wasn't defined in the Token's header + Jwk jwk = jwkProvider.get(keyId); + return (RSAPublicKey) jwk.getPublicKey(); + } + + @Override + public RSAPrivateKey getPrivateKey() { + return signingKey; + } + + @Override + public String getSigningKeyId() { + return signingKeyId; + } +}; +Algorithm algorithm = Algorithm.RSA256(keyProvider); +//Use the Algorithm to create and verify JWTs. +``` + + ### Create and Sign a Token You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the `Algorithm` instance. From 11e96ade7db83ea4897de1d2e725a212c70b56f5 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 3 May 2017 18:32:19 -0300 Subject: [PATCH 053/355] update KeyProvider interface. Update readme. --- README.md | 32 ++++++---- .../main/java/com/auth0/jwt/JWTCreator.java | 3 +- .../com/auth0/jwt/algorithms/Algorithm.java | 14 ++-- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 20 +++--- .../auth0/jwt/algorithms/RSAAlgorithm.java | 8 +-- ...KeyProvider.java => ECDSAKeyProvider.java} | 2 +- .../com/auth0/jwt/interfaces/KeyProvider.java | 12 ++-- .../java/com/auth0/jwt/JWTCreatorTest.java | 26 ++++---- .../auth0/jwt/algorithms/AlgorithmTest.java | 14 ++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 64 +++++++++---------- .../jwt/algorithms/RSAAlgorithmTest.java | 20 +++--- 11 files changed, 112 insertions(+), 103 deletions(-) rename lib/src/main/java/com/auth0/jwt/interfaces/{ECKeyProvider.java => ECDSAKeyProvider.java} (69%) diff --git a/README.md b/README.md index 28536ede..0853aa21 100644 --- a/README.md +++ b/README.md @@ -64,38 +64,46 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); #### Using a KeyProvider: -By using a `KeyProvider` the library delegates the decision of which key to use in each case to the user. For the verification process, this means that the provider will be asked for a `PublicKey` with a given **Key Id** value. Your provider implementation should have the logic to fetch the right key, for example by parsing a JWKS file from a public domain like [auth0/jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) does. For the signing process, this means that the provider will be asked for a `PrivateKey` and it's associated **Key Id**, so it can set it in the Token's header for future verification in the same way. Check the [IETF draft](https://tools.ietf.org/html/rfc7517) for more information on how to implement this. +By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: + +- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). +- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. +- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. + The following snippet uses example classes showing how this would work: ```java -final MyOwnJwkProvider jwkProvider = new MyOwnJwkProvider("{JWKS_FILE_HOST}"); -final RSAPrivateKey signingKey = //Get the key instance -final String signingKeyId = //Create an Id for the above key +final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); +final RSAPrivateKey privateKey = //Get the key instance +final String privateKeyId = //Create an Id for the above key RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override - public RSAPublicKey getPublicKey(String keyId) { - //Value might be null if it wasn't defined in the Token's header - Jwk jwk = jwkProvider.get(keyId); - return (RSAPublicKey) jwk.getPublicKey(); + public RSAPublicKey getPublicKeyById(String kid) { + //Received 'kid' value might be null if it wasn't defined in the Token's header + RSAPublicKey publicKey = jwkStore.get(kid); + return (RSAPublicKey) publicKey; } @Override public RSAPrivateKey getPrivateKey() { - return signingKey; + return privateKey; } @Override - public String getSigningKeyId() { - return signingKeyId; + public String getPrivateKeyId() { + return privateKeyId; } }; + Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs. ``` +> For simple key rotation using JWKs try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. + ### Create and Sign a Token @@ -271,7 +279,7 @@ When creating a Token with the `JWT.create()` you can specify header Claims by c ```java Map headerClaims = new HashMap(); -headerclaims.put("owner", "auth0"); +headerClaims.put("owner", "auth0"); String token = JWT.create() .withHeader(headerClaims) .sign(algorithm); diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 10c257f8..a0889876 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -77,6 +77,7 @@ public Builder withHeader(Map headerClaims) { /** * Add a specific Key Id ("kid") claim to the Header. + * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, the 'kid' value will be taken from that provider and this one will be ignored. * * @param keyId the Key Id value. * @return this same Builder instance. @@ -304,7 +305,7 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); headerClaims.put(PublicClaims.TYPE, "JWT"); String signingKeyId = algorithm.getSigningKeyId(); - if (!headerClaims.containsKey(PublicClaims.KEY_ID) && signingKeyId != null) { + if (signingKeyId != null) { withKeyId(signingKeyId); } return new JWTCreator(algorithm, headerClaims, payloadClaims).sign(); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index c34702d0..12080a02 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -3,7 +3,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import java.io.UnsupportedEncodingException; @@ -208,7 +208,7 @@ public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { * @return a valid ECDSA256 Algorithm. * @throws IllegalArgumentException if the Key Provider is null. */ - public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgumentException { + public static Algorithm ECDSA256(ECDSAKeyProvider keyProvider) throws IllegalArgumentException { return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider); } @@ -230,7 +230,7 @@ public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA256 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECKeyProvider)} + * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECDSAKeyProvider)} */ @Deprecated public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { @@ -246,7 +246,7 @@ public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException { * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the Key Provider is null. */ - public static Algorithm ECDSA384(ECKeyProvider keyProvider) throws IllegalArgumentException { + public static Algorithm ECDSA384(ECDSAKeyProvider keyProvider) throws IllegalArgumentException { return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, keyProvider); } @@ -268,7 +268,7 @@ public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA384 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} or {@link #ECDSA384(ECKeyProvider)} + * @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} or {@link #ECDSA384(ECDSAKeyProvider)} */ @Deprecated public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { @@ -284,7 +284,7 @@ public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException { * @return a valid ECDSA512 Algorithm. * @throws IllegalArgumentException if the Key Provider is null. */ - public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgumentException { + public static Algorithm ECDSA512(ECDSAKeyProvider keyProvider) throws IllegalArgumentException { return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider); } @@ -306,7 +306,7 @@ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) * @param key the key to use in the verify or signing instance. * @return a valid ECDSA512 Algorithm. * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} or {@link #ECDSA512(ECKeyProvider)} + * @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} or {@link #ECDSA512(ECDSAKeyProvider)} */ @Deprecated public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException { 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 714cc6c1..25201132 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -3,7 +3,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; @@ -15,12 +15,12 @@ class ECDSAAlgorithm extends Algorithm { - private final ECKeyProvider keyProvider; + private final ECDSAKeyProvider keyProvider; private final CryptoHelper crypto; private final int ecNumberSize; //Visible for testing - ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { super(id, algorithm); if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); @@ -30,7 +30,7 @@ class ECDSAAlgorithm extends Algorithm { this.ecNumberSize = ecNumberSize; } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } @@ -40,7 +40,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); try { - ECPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId()); + ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } @@ -72,7 +72,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { @Override public String getSigningKeyId() { - return keyProvider.getSigningKeyId(); + return keyProvider.getPrivateKeyId(); } private boolean isDERSignature(byte[] signature) { @@ -138,13 +138,13 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) { } //Visible for testing - static ECKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) { + static ECDSAKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) { if (publicKey == null && privateKey == null) { throw new IllegalArgumentException("Both provided Keys cannot be null."); } - return new ECKeyProvider() { + return new ECDSAKeyProvider() { @Override - public ECPublicKey getPublicKey(String keyId) { + public ECPublicKey getPublicKeyById(String keyId) { return publicKey; } @@ -154,7 +154,7 @@ public ECPrivateKey getPrivateKey() { } @Override - public String getSigningKeyId() { + public String getPrivateKeyId() { return null; } }; diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 5083fbc7..b423e159 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -38,7 +38,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); try { - RSAPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId()); + RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } @@ -66,7 +66,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { @Override public String getSigningKeyId() { - return keyProvider.getSigningKeyId(); + return keyProvider.getPrivateKeyId(); } //Visible for testing @@ -76,7 +76,7 @@ static RSAKeyProvider providerForKeys(final RSAPublicKey publicKey, final RSAPri } return new RSAKeyProvider() { @Override - public RSAPublicKey getPublicKey(String keyId) { + public RSAPublicKey getPublicKeyById(String keyId) { return publicKey; } @@ -86,7 +86,7 @@ public RSAPrivateKey getPrivateKey() { } @Override - public String getSigningKeyId() { + public String getPrivateKeyId() { return null; } }; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/ECDSAKeyProvider.java similarity index 69% rename from lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java rename to lib/src/main/java/com/auth0/jwt/interfaces/ECDSAKeyProvider.java index ff14f9e8..55df451d 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/ECDSAKeyProvider.java @@ -6,5 +6,5 @@ /** * Elliptic Curve (EC) Public/Private Key provider. */ -public interface ECKeyProvider extends KeyProvider { +public interface ECDSAKeyProvider extends KeyProvider { } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index c891e57d..fa3b13c9 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -12,24 +12,24 @@ interface KeyProvider { /** - * Getter for the Public Key instance, used to verify the signature. + * Getter for the Public Key instance with the given Id. Used to verify the signature on the JWT verification stage. * * @param keyId the Key Id specified in the Token's Header or null if none is available. Provides a hint on which Public Key to use to verify the token's signature. * @return the Public Key instance */ - U getPublicKey(String keyId); + U getPublicKeyById(String keyId); /** - * Getter for the Private Key instance, used to sign the content. + * Getter for the Private Key instance. Used to sign the content on the JWT signing stage. * * @return the Private Key instance */ R getPrivateKey(); /** - * Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header if no other "Key Id" has been set already. + * Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header. * - * @return the Key Id that identifies the Signing Key or null if it's not specified. + * @return the Key Id that identifies the Private Key or null if it's not specified. */ - String getSigningKeyId(); + String getPrivateKeyId(); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 355482b1..ab263bad 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -1,7 +1,7 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; @@ -66,10 +66,10 @@ public void shouldAddKeyId() throws Exception { } @Test - public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingRSAAlgorithms() throws Exception { + public void shouldAddKeyIdIfAvailableFromRSAAlgorithms() throws Exception { RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"); RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); String signed = JWTCreator.init() @@ -82,10 +82,10 @@ public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingRSAAlgorithms() thro } @Test - public void shouldNotOverwriteKeyIdIfAlreadySetUsingRSAAlgorithms() throws Exception { + public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception { RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA"); RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); String signed = JWTCreator.init() @@ -95,14 +95,14 @@ public void shouldNotOverwriteKeyIdIfAlreadySetUsingRSAAlgorithms() throws Excep assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id")); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @Test - public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingECDSAAlgorithms() throws Exception { + public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception { ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("my-key-id"); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); String signed = JWTCreator.init() @@ -115,10 +115,10 @@ public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingECDSAAlgorithms() th } @Test - public void shouldNotOverwriteKeyIdIfAlreadySetUsingECDSAAlgorithms() throws Exception { + public void shouldNotOverwriteKeyIdIfAddedFromECDSAAlgorithms() throws Exception { ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("my-key-id"); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); String signed = JWTCreator.init() @@ -128,7 +128,7 @@ public void shouldNotOverwriteKeyIdIfAlreadySetUsingECDSAAlgorithms() throws Exc assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id")); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index e66bf482..6dcbbae9 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -1,6 +1,6 @@ package com.auth0.jwt.algorithms; -import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import org.junit.Rule; import org.junit.Test; @@ -156,7 +156,7 @@ public void shouldThrowECDSA256InstanceWithNullKeys() throws Exception { public void shouldThrowECDSA256InstanceWithNullKeyProvider() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Key Provider cannot be null."); - ECKeyProvider provider = null; + ECDSAKeyProvider provider = null; Algorithm.ECDSA256(provider); } @@ -179,7 +179,7 @@ public void shouldThrowECDSA384InstanceWithNullKeys() throws Exception { public void shouldThrowECDSA384InstanceWithNullKeyProvider() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Key Provider cannot be null."); - ECKeyProvider provider = null; + ECDSAKeyProvider provider = null; Algorithm.ECDSA384(provider); } @@ -202,7 +202,7 @@ public void shouldThrowECDSA512InstanceWithNullKeys() throws Exception { public void shouldThrowECDSA512InstanceWithNullKeyProvider() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("The Key Provider cannot be null."); - ECKeyProvider provider = null; + ECDSAKeyProvider provider = null; Algorithm.ECDSA512(provider); } @@ -437,7 +437,7 @@ public void shouldCreateECDSA256AlgorithmWithBothKeys() throws Exception { @Test public void shouldCreateECDSA256AlgorithmWithProvider() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); Algorithm algorithm = Algorithm.ECDSA256(provider); assertThat(algorithm, is(notNullValue())); @@ -482,7 +482,7 @@ public void shouldCreateECDSA384AlgorithmWithBothKeys() throws Exception { @Test public void shouldCreateECDSA384AlgorithmWithProvider() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); Algorithm algorithm = Algorithm.ECDSA384(provider); assertThat(algorithm, is(notNullValue())); @@ -527,7 +527,7 @@ public void shouldCreateECDSA512AlgorithmWithBothKeys() throws Exception { @Test public void shouldCreateECDSA512AlgorithmWithProvider() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); Algorithm algorithm = Algorithm.ECDSA512(provider); assertThat(algorithm, is(notNullValue())); 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 1a1fee01..09a9e264 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -3,7 +3,7 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.interfaces.ECKeyProvider; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -80,9 +80,9 @@ public void shouldPassECDSA256VerificationWithDERSignatureWithBothKeys() throws @Test public void shouldPassECDSA256VerificationWithProvidedPublicKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); - when(provider.getPublicKey("my-key-id")).thenReturn((ECPublicKey) publicKey); + 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)); @@ -94,8 +94,8 @@ public void shouldFailECDSA256VerificationWhenProvidedPublicKeyIsNull() throws E 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."))); - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(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)); @@ -195,9 +195,9 @@ public void shouldPassECDSA384VerificationWithDERSignatureWithBothKeys() throws @Test public void shouldPassECDSA384VerificationWithProvidedPublicKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); - when(provider.getPublicKey("my-key-id")).thenReturn((ECPublicKey) publicKey); + 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)); @@ -209,8 +209,8 @@ public void shouldFailECDSA384VerificationWhenProvidedPublicKeyIsNull() throws E 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."))); - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(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)); @@ -310,9 +310,9 @@ public void shouldPassECDSA512VerificationWithDERSignatureWithBothKeys() throws @Test public void shouldPassECDSA512VerificationWithProvidedPublicKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - when(provider.getPublicKey("my-key-id")).thenReturn((ECPublicKey) publicKey); + 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)); @@ -324,8 +324,8 @@ public void shouldFailECDSA512VerificationWhenProvidedPublicKeyIsNull() throws E 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."))); - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(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)); @@ -407,7 +407,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, provider); algorithm.verify(JWT.decode(jwt)); } @@ -424,7 +424,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + 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)); @@ -442,7 +442,7 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + 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)); @@ -460,7 +460,7 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + 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)); @@ -501,11 +501,11 @@ public void shouldDoECDSA256SigningWithBothKeys() throws Exception { @Test public void shouldDoECDSA256SigningWithProvidedPrivateKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + 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.getPublicKey(null)).thenReturn((ECPublicKey) publicKey); + 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); @@ -524,7 +524,7 @@ public void shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA256(provider); algorithm.sign(new byte[0]); @@ -570,11 +570,11 @@ public void shouldDoECDSA384SigningWithBothKeys() throws Exception { @Test public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + 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.getPublicKey(null)).thenReturn((ECPublicKey) publicKey); + 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); @@ -593,7 +593,7 @@ public void shouldFailOnECDSA384SigningWhenProvidedPrivateKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA384(provider); algorithm.sign(new byte[0]); @@ -640,11 +640,11 @@ public void shouldDoECDSA512SigningWithBothKeys() throws Exception { @Test public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); + 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.getPublicKey(null)).thenReturn((ECPublicKey) publicKey); + 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); @@ -663,7 +663,7 @@ public void shouldFailOnECDSA512SigningWhenProvidedPrivateKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Private Key is null."))); - ECKeyProvider provider = mock(ECKeyProvider.class); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA512(provider); algorithm.sign(new byte[0]); @@ -692,7 +692,7 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -709,7 +709,7 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -726,7 +726,7 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); } @@ -735,7 +735,7 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { public void shouldReturnNullSigningKeyIdIfCreatedWithDefaultProvider() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); ECPrivateKey privateKey = mock(ECPrivateKey.class); - ECKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm("some-alg", "some-algorithm", 32, provider); assertThat(algorithm.getSigningKeyId(), is(nullValue())); @@ -743,8 +743,8 @@ public void shouldReturnNullSigningKeyIdIfCreatedWithDefaultProvider() throws Ex @Test public void shouldReturnSigningKeyIdFromProvider() throws Exception { - ECKeyProvider provider = mock(ECKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("keyId"); + 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")); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index 939f7059..9025f9e4 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -55,7 +55,7 @@ public void shouldPassRSA256VerificationWithBothKeys() throws Exception { public void shouldPassRSA256VerificationWithProvidedPublicKey() throws Exception { RSAKeyProvider provider = mock(RSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); - when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById("my-key-id")).thenReturn((RSAPublicKey) publicKey); String jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.jXrbue3xJmnzWH9kU-uGeCTtgbQEKbch8uHd4Z52t86ncNyepfusl_bsyLJIcxMwK7odRzKiSE9efV9JaRSEDODDBdMeCzODFx82uBM7e46T1NLVSmjYIM7Hcfh81ZeTIk-hITvgtL6hvTdeJWOCZAB0bs18qSVW5SvursRUhY38xnhuNI6HOHCtqp7etxWAu6670L53I3GtXsmi6bXIzv_0v1xZcAFg4HTvXxfhfj3oCqkSs2nC27mHxBmQtmZKWmXk5HzVUyPRwTUWx5wHPT_hCsGer-CMCAyGsmOg466y1KDqf7ogpMYojfVZGWBsyA39LO1oWZ4Ryomkn8t5Vg"; Algorithm algorithm = Algorithm.RSA256(provider); algorithm.verify(JWT.decode(jwt)); @@ -68,7 +68,7 @@ public void shouldFailRSA256VerificationWhenProvidedPublicKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(null); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); String jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.jXrbue3xJmnzWH9kU-uGeCTtgbQEKbch8uHd4Z52t86ncNyepfusl_bsyLJIcxMwK7odRzKiSE9efV9JaRSEDODDBdMeCzODFx82uBM7e46T1NLVSmjYIM7Hcfh81ZeTIk-hITvgtL6hvTdeJWOCZAB0bs18qSVW5SvursRUhY38xnhuNI6HOHCtqp7etxWAu6670L53I3GtXsmi6bXIzv_0v1xZcAFg4HTvXxfhfj3oCqkSs2nC27mHxBmQtmZKWmXk5HzVUyPRwTUWx5wHPT_hCsGer-CMCAyGsmOg466y1KDqf7ogpMYojfVZGWBsyA39LO1oWZ4Ryomkn8t5Vg"; Algorithm algorithm = Algorithm.RSA256(provider); algorithm.verify(JWT.decode(jwt)); @@ -112,7 +112,7 @@ public void shouldPassRSA384VerificationWithBothKeys() throws Exception { public void shouldPassRSA384VerificationWithProvidedPublicKey() throws Exception { RSAKeyProvider provider = mock(RSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); - when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById("my-key-id")).thenReturn((RSAPublicKey) publicKey); String jwt = "eyJhbGciOiJSUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.ITNTVCT7ercumZKHV4-BXGkJwwa7fyF3CnSfEvm09fDFSkaseDxNo_75WLDmK9WM8RMHTPvkpHcTKm4guYEbC_la7RzFIKpU72bppzQojggSmWWXt_6zq50QP2t5HFMebote1zxhp8ccEdSCX5pyY6J2sm9kJ__HKK32KxIVCTjVCz-bFBS60oG35aYEySdKsxuUdWbD5FQ9I16Ony2x0EPvmlL3GPiAPmgjSFp3LtcBIbCDaoonM7iuDRGIQiDN_n2FKKb1Bt4_38uWPtTkwRpNalt6l53Y3JDdzGI5fMrMo3RQnQlAJxUJKD0eL6dRAA645IVIIXucHwuhgGGIVw"; Algorithm algorithm = Algorithm.RSA384(provider); algorithm.verify(JWT.decode(jwt)); @@ -125,7 +125,7 @@ public void shouldFailRSA384VerificationWhenProvidedPublicKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(null); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); String jwt = "eyJhbGciOiJSUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.ITNTVCT7ercumZKHV4-BXGkJwwa7fyF3CnSfEvm09fDFSkaseDxNo_75WLDmK9WM8RMHTPvkpHcTKm4guYEbC_la7RzFIKpU72bppzQojggSmWWXt_6zq50QP2t5HFMebote1zxhp8ccEdSCX5pyY6J2sm9kJ__HKK32KxIVCTjVCz-bFBS60oG35aYEySdKsxuUdWbD5FQ9I16Ony2x0EPvmlL3GPiAPmgjSFp3LtcBIbCDaoonM7iuDRGIQiDN_n2FKKb1Bt4_38uWPtTkwRpNalt6l53Y3JDdzGI5fMrMo3RQnQlAJxUJKD0eL6dRAA645IVIIXucHwuhgGGIVw"; Algorithm algorithm = Algorithm.RSA384(provider); algorithm.verify(JWT.decode(jwt)); @@ -169,7 +169,7 @@ public void shouldPassRSA512VerificationWithBothKeys() throws Exception { public void shouldPassRSA512VerificationWithProvidedPublicKey() throws Exception { RSAKeyProvider provider = mock(RSAKeyProvider.class); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); - when(provider.getPublicKey("my-key-id")).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById("my-key-id")).thenReturn((RSAPublicKey) publicKey); String jwt = "eyJhbGciOiJSUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.GpHv85Q8tAU_6hNWsmO0GEpO1qz9lmK3NKeAcemysz9MGo4FXWn8xbD8NjCfzZ8EWphm65M0NArKSjpKHO5-gcNsQxLBVfSED1vzcoaZH_Vy5Rp1M76dGH7JghB_66KrpfyMxer_yRJb-KXesNvIroDGilLQF2ENG-IfLF5nBKlDiVHmPaqr3pm1q20fNLhegkSRca4BJ5VdIlT6kOqE_ykVyCBqzD_oXp3LKO_ARnxoeB9SegIW1fy_3tuxSTKYsCZiOfiyVEXXblAuY3pSLZnGvgeBRnfvmWXDWhP0vVUFtYJBF09eULvvUMVqWcrjUG9gDzzzT7veiY_fHd_x8g"; Algorithm algorithm = Algorithm.RSA512(provider); algorithm.verify(JWT.decode(jwt)); @@ -182,7 +182,7 @@ public void shouldFailRSA512VerificationWhenProvidedPublicKeyIsNull() throws Exc exception.expectCause(isA(IllegalStateException.class)); exception.expectCause(hasMessage(is("The given Public Key is null."))); RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getPublicKey("my-key-id")).thenReturn(null); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); String jwt = "eyJhbGciOiJSUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.GpHv85Q8tAU_6hNWsmO0GEpO1qz9lmK3NKeAcemysz9MGo4FXWn8xbD8NjCfzZ8EWphm65M0NArKSjpKHO5-gcNsQxLBVfSED1vzcoaZH_Vy5Rp1M76dGH7JghB_66KrpfyMxer_yRJb-KXesNvIroDGilLQF2ENG-IfLF5nBKlDiVHmPaqr3pm1q20fNLhegkSRca4BJ5VdIlT6kOqE_ykVyCBqzD_oXp3LKO_ARnxoeB9SegIW1fy_3tuxSTKYsCZiOfiyVEXXblAuY3pSLZnGvgeBRnfvmWXDWhP0vVUFtYJBF09eULvvUMVqWcrjUG9gDzzzT7veiY_fHd_x8g"; Algorithm algorithm = Algorithm.RSA512(provider); algorithm.verify(JWT.decode(jwt)); @@ -308,7 +308,7 @@ public void shouldDoRSA256SigningWithProvidedPrivateKey() throws Exception { PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); - when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA256(provider); String jwtContent = String.format("%s.%s", RS256Header, auth0IssPayload); byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); @@ -383,7 +383,7 @@ public void shouldDoRSA384SigningWithProvidedPrivateKey() throws Exception { PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); - when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA384(provider); String jwtContent = String.format("%s.%s", RS384Header, auth0IssPayload); byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); @@ -458,7 +458,7 @@ public void shouldDoRSA512SigningWithProvidedPrivateKey() throws Exception { PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); - when(provider.getPublicKey(null)).thenReturn((RSAPublicKey) publicKey); + when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA512(provider); String jwtContent = String.format("%s.%s", RS512Header, auth0IssPayload); byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); @@ -558,7 +558,7 @@ public void shouldReturnNullSigningKeyIdIfCreatedWithDefaultProvider() throws Ex @Test public void shouldReturnSigningKeyIdFromProvider() throws Exception { RSAKeyProvider provider = mock(RSAKeyProvider.class); - when(provider.getSigningKeyId()).thenReturn("keyId"); + when(provider.getPrivateKeyId()).thenReturn("keyId"); Algorithm algorithm = new RSAAlgorithm("some-alg", "some-algorithm", provider); assertThat(algorithm.getSigningKeyId(), is("keyId")); From eb41281c501465a73f3f3795264ebf8c0dad4b11 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 4 May 2017 17:21:11 -0300 Subject: [PATCH 054/355] Release 3.2.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ README.md | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb9c107..69081793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## [3.2.0](https://github.com/auth0/java-jwt/tree/3.2.0) (2017-05-04) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.1.0...3.2.0) +**Closed issues** +- Claim.isNull() returns true for JSON Object constructed claims [\#160](https://github.com/auth0/java-jwt/issues/160) +- Incorrectly rejects whitespace after JSON header as invalid [\#144](https://github.com/auth0/java-jwt/issues/144) +- No token type [\#136](https://github.com/auth0/java-jwt/issues/136) +- Timestamps are limited by Integer/int to 2038-01-19T04:14:07.000+0100 [\#132](https://github.com/auth0/java-jwt/issues/132) + +**Added** +- Refactor KeyProvider to receive the "Key Id" [\#167](https://github.com/auth0/java-jwt/pull/167) ([lbalmaceda](https://github.com/lbalmaceda)) +- Add Sign/Verify of Long type claims [\#157](https://github.com/auth0/java-jwt/pull/157) ([vrancic](https://github.com/vrancic)) +- added date validation dedicated exception [\#155](https://github.com/auth0/java-jwt/pull/155) ([Spyna](https://github.com/Spyna)) +- Allow to get a Claim as Map [\#152](https://github.com/auth0/java-jwt/pull/152) ([lbalmaceda](https://github.com/lbalmaceda)) +- Add Algorithm KeyProvider interface [\#149](https://github.com/auth0/java-jwt/pull/149) ([lbalmaceda](https://github.com/lbalmaceda)) +- Instantiate RSA/EC Algorithm with both keys [\#147](https://github.com/auth0/java-jwt/pull/147) ([lbalmaceda](https://github.com/lbalmaceda)) +- Add Key Id setter and set JWT Type after signing [\#138](https://github.com/auth0/java-jwt/pull/138) ([lbalmaceda](https://github.com/lbalmaceda)) + +**Changed** +- Change the JWT.decode() return type to DecodedJWT [\#150](https://github.com/auth0/java-jwt/pull/150) ([lbalmaceda](https://github.com/lbalmaceda)) + +**Fixed** +- Fix Claim.isNull() method for JSON Objects [\#161](https://github.com/auth0/java-jwt/pull/161) ([lbalmaceda](https://github.com/lbalmaceda)) +- Accept blanks, new line and carriage returns on JSON [\#151](https://github.com/auth0/java-jwt/pull/151) ([lbalmaceda](https://github.com/lbalmaceda)) +- Fix Date value conversion [\#137](https://github.com/auth0/java-jwt/pull/137) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.1.0](https://github.com/auth0/java-jwt/tree/3.1.0) (2017-01-04) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.0.2...3.1.0) diff --git a/README.md b/README.md index 0853aa21..a3ea5a68 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.1.0 + 3.2.0 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.1.0' +compile 'com.auth0:java-jwt:3.2.0' ``` ## Available Algorithms From 89dac3ab90577c75db5eeca54b166a1d4e20421c Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 21 Jun 2017 17:36:01 -0300 Subject: [PATCH 055/355] add public and private key notes in the README.md file --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3ea5a68..3f012f7e 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ The library implements JWT Verification and Signing using the following algorith The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. +When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. + + #### Using static secrets or keys: ```java @@ -91,7 +94,7 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { public RSAPrivateKey getPrivateKey() { return privateKey; } - + @Override public String getPrivateKeyId() { return privateKeyId; From 804b1bbb87271424dad75e1be893f31e410e9114 Mon Sep 17 00:00:00 2001 From: Philippe Lovis Date: Mon, 28 Aug 2017 16:28:39 +0200 Subject: [PATCH 056/355] Don't instantiate unnecessary exception for valid JSON --- lib/src/main/java/com/auth0/jwt/impl/JWTParser.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index 1efb825e..45854785 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -49,14 +49,17 @@ static ObjectMapper getDefaultObjectMapper() { @SuppressWarnings("WeakerAccess") T convertFromJSON(String json, Class tClazz) throws JWTDecodeException { - JWTDecodeException exception = new JWTDecodeException(String.format("The string '%s' doesn't have a valid JSON format.", json)); if (json == null) { - throw exception; + throw exceptionForInvalidJson(null); } try { return mapper.readValue(json, tClazz); } catch (IOException e) { - throw exception; + throw exceptionForInvalidJson(json); } } + + private JWTDecodeException exceptionForInvalidJson(String json) { + return new JWTDecodeException(String.format("The string '%s' doesn't have a valid JSON format.", json)); + } } From f2b5b5b4efb01d3b5490b5bf78c7f70ff9320891 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 5 Sep 2017 10:57:48 -0300 Subject: [PATCH 057/355] rename license file --- LICENCE => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENCE => LICENSE (100%) diff --git a/LICENCE b/LICENSE similarity index 100% rename from LICENCE rename to LICENSE From e30e584358b23f950c3b96cfc76830f80c74d254 Mon Sep 17 00:00:00 2001 From: James Bench Date: Wed, 27 Sep 2017 16:55:03 +0100 Subject: [PATCH 058/355] Add an interface for JWTVerifier. --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 3 ++- .../main/java/com/auth0/jwt/interfaces/JWTVerifier.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6f513c7e..7e5ffed9 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -14,7 +14,7 @@ * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. */ @SuppressWarnings("WeakerAccess") -public final class JWTVerifier { +public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; final Map claims; private final Clock clock; @@ -348,6 +348,7 @@ private void requireClaim(String name, Object value) { * @throws TokenExpiredException if the token has expired. * @throws InvalidClaimException if a claim contained a different value than the expected one. */ + @Override public DecodedJWT verify(String token) throws JWTVerificationException { DecodedJWT jwt = JWT.decode(token); verifyAlgorithm(jwt, algorithm); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java new file mode 100644 index 00000000..49f285aa --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -0,0 +1,7 @@ +package com.auth0.jwt.interfaces; + +import com.auth0.jwt.exceptions.JWTVerificationException; + +public interface JWTVerifier { + DecodedJWT verify(String token) throws JWTVerificationException; +} From bb5b897b2564590b90b815dc3a9c61db60f536b8 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 23 Oct 2017 14:14:15 -0300 Subject: [PATCH 059/355] circle 2.0 --- .circleci/config.yml | 62 +++++++++++++++++++ circle.yml | 20 ------ .../com/auth0/jwt/impl/NullClaimTest.java | 1 - 3 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..909f794c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,62 @@ +version: 2 +jobs: + java_7_build: + docker: + - image: openjdk:7u121-jdk + steps: + - checkout + - run: chmod +x gradlew + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + # run tests! + - run: ./gradlew clean check jacocoTestReport --continue --console=plain + - run: + name: Upload Coverage + when: on_success + command: bash <(curl -s https://codecov.io/bash) + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "build.gradle" }} + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb + + java_8_build: + docker: + - image: openjdk:8-jdk + steps: + - checkout + - run: chmod +x gradlew + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + # run tests! + - run: ./gradlew clean check jacocoTestReport --continue --console=plain + - run: + name: Upload Coverage + when: on_success + command: bash <(curl -s https://codecov.io/bash) + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "build.gradle" }} + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb + +workflows: + version: 2 + build: + jobs: + - java_7_build + - java_8_build diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 07d14f09..00000000 --- a/circle.yml +++ /dev/null @@ -1,20 +0,0 @@ -# -# Build configuration for Circle CI -# - -machine: - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - java: - version: oraclejdk7 - -dependencies: - cache_directories: - - ~/.gradle - -test: - override: - - ./gradlew clean check jacocoTestReport --continue --console=plain - post: - - bash <(curl -s https://codecov.io/bash) diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index 72b937f5..d8ddd516 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -2,7 +2,6 @@ import org.junit.Before; import org.junit.Test; -import org.omg.CORBA.Object; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; From 064a01ebfa5b832c260798dc777f5d45f94e6c1f Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 18 Oct 2017 15:43:24 -0300 Subject: [PATCH 060/355] 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 061/355] 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 From d6a5f7c7970a488ceb2c26373d035eb10d74bec9 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 6 Nov 2017 12:22:35 -0300 Subject: [PATCH 062/355] Release 3.3.0 --- CHANGELOG.md | 9 +++++++++ README.md | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69081793..cfb8210b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [3.3.0](https://github.com/auth0/java-jwt/tree/3.3.0) (2017-11-06) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.2.0...3.3.0) +**Closed issues** +- Wrong ES256 signature length [\#187](https://github.com/auth0/java-jwt/issues/187) + +**Fixed** +- Rework ECDSA [\#212](https://github.com/auth0/java-jwt/pull/212) ([lbalmaceda](https://github.com/lbalmaceda)) +- Instantiate exception only when required [\#198](https://github.com/auth0/java-jwt/pull/198) ([rumdidumdum](https://github.com/rumdidumdum)) + ## [3.2.0](https://github.com/auth0/java-jwt/tree/3.2.0) (2017-05-04) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.1.0...3.2.0) **Closed issues** diff --git a/README.md b/README.md index 3f012f7e..4ce86422 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.2.0 + 3.3.0 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.2.0' +compile 'com.auth0:java-jwt:3.3.0' ``` ## Available Algorithms From f229898e3e45bb05e8dcceb4e4b8d16f1bac9ae5 Mon Sep 17 00:00:00 2001 From: Oliver Becker Date: Wed, 21 Feb 2018 21:40:42 +0100 Subject: [PATCH 063/355] Fix for issue #236 - refactored HMACAlgorithm so that it doesn't throw an UnsupportedEncodingException --- README.md | 4 ---- .../main/java/com/auth0/jwt/algorithms/Algorithm.java | 10 +++------- .../java/com/auth0/jwt/algorithms/HMACAlgorithm.java | 8 +++----- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4ce86422..e98f1f79 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,6 @@ try { String token = JWT.create() .withIssuer("auth0") .sign(algorithm); -} catch (UnsupportedEncodingException exception){ - //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. } @@ -159,8 +157,6 @@ try { .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); -} catch (UnsupportedEncodingException exception){ - //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 12080a02..3cc66d07 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -6,7 +6,6 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.io.UnsupportedEncodingException; import java.security.interfaces.*; /** @@ -138,9 +137,8 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. - * @throws UnsupportedEncodingException if the current Java platform implementation doesn't support the UTF-8 character encoding. */ - public static Algorithm HMAC256(String secret) throws IllegalArgumentException, UnsupportedEncodingException { + public static Algorithm HMAC256(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS256", "HmacSHA256", secret); } @@ -150,9 +148,8 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException, * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. - * @throws UnsupportedEncodingException if the current Java platform implementation doesn't support the UTF-8 character encoding. */ - public static Algorithm HMAC384(String secret) throws IllegalArgumentException, UnsupportedEncodingException { + public static Algorithm HMAC384(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS384", "HmacSHA384", secret); } @@ -162,9 +159,8 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException, * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. - * @throws UnsupportedEncodingException if the current Java platform implementation doesn't support the UTF-8 character encoding. */ - public static Algorithm HMAC512(String secret) throws IllegalArgumentException, UnsupportedEncodingException { + public static Algorithm HMAC512(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS512", "HmacSHA512", secret); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index afe1b30e..670928f8 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -3,10 +3,8 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.CharEncoding; import org.apache.commons.codec.binary.Base64; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -30,16 +28,16 @@ class HMACAlgorithm extends Algorithm { this(new CryptoHelper(), id, algorithm, secretBytes); } - HMACAlgorithm(String id, String algorithm, String secret) throws IllegalArgumentException, UnsupportedEncodingException { + HMACAlgorithm(String id, String algorithm, String secret) throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, getSecretBytes(secret)); } //Visible for testing - static byte[] getSecretBytes(String secret) throws IllegalArgumentException, UnsupportedEncodingException { + static byte[] getSecretBytes(String secret) throws IllegalArgumentException { if (secret == null) { throw new IllegalArgumentException("The Secret cannot be null"); } - return secret.getBytes(CharEncoding.UTF_8); + return secret.getBytes(StandardCharsets.UTF_8); } @Override From ed94ed499f28e60b16d285370938a3356f599ac2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 21 Feb 2018 16:48:43 -0300 Subject: [PATCH 064/355] throw JWTDecodeException when date claim format is invalid --- .../main/java/com/auth0/jwt/impl/PayloadDeserializer.java | 5 ++++- lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java | 2 +- .../java/com/auth0/jwt/impl/PayloadDeserializerTest.java | 8 +++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 38068872..8f872cb4 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -65,9 +65,12 @@ List getStringOrArray(Map tree, String claimName) thro Date getDateFromSeconds(Map tree, String claimName) { JsonNode node = tree.get(claimName); - if (node == null || node.isNull() || !node.canConvertToLong()) { + if (node == null || node.isNull()) { return null; } + if (!node.canConvertToLong()) { + throw new JWTDecodeException(String.format("The claim '%s' contained an unexpected value.", claimName)); + } final long ms = node.asLong() * 1000; return new Date(ms); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index a482685e..e164dc07 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -275,7 +275,7 @@ public void shouldGetCustomMapClaim() throws Exception { @Test public void shouldGetAvailableClaims() throws Exception { - DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTIzNDU2Nzg5MCIsIm5iZiI6IjEyMzQ1Njc4OTAiLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.TX9Ct4feGp9YyeGK9Zl91tO0YBOrguJ4As9jeqgHdZQ"); + DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MCwibmJmIjoxMjM0NTY3ODkwLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.2_0nxDPJwOk64U5V5V9pt8U92jTPJbGsHYQ35HYhbdE"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaims(), is(notNullValue())); assertThat(jwt.getClaims(), is(instanceOf(Map.class))); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 014ae838..7fe9145c 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -198,13 +198,15 @@ public void shouldGetNullDateWhenParsingNull() throws Exception { } @Test - public void shouldGetNullDateWhenParsingNonNumericNode() throws Exception { + public void shouldThrowWhenParsingNonNumericNode() throws Exception { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The claim 'key' contained an unexpected value."); + Map tree = new HashMap<>(); TextNode node = new TextNode("123456789"); tree.put("key", node); - Date date = deserializer.getDateFromSeconds(tree, "key"); - assertThat(date, is(nullValue())); + deserializer.getDateFromSeconds(tree, "key"); } @Test From 95e7e517c1027af336ac2933acd8e6e3c4b397e6 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 29 May 2018 12:07:49 -0300 Subject: [PATCH 065/355] change exception message --- lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java | 2 +- .../test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 8f872cb4..43047bfc 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -69,7 +69,7 @@ Date getDateFromSeconds(Map tree, String claimName) { return null; } if (!node.canConvertToLong()) { - throw new JWTDecodeException(String.format("The claim '%s' contained an unexpected value.", claimName)); + throw new JWTDecodeException(String.format("The claim '%s' contained a non-numeric date value.", claimName)); } final long ms = node.asLong() * 1000; return new Date(ms); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 7fe9145c..2e2cabed 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -200,7 +200,7 @@ public void shouldGetNullDateWhenParsingNull() throws Exception { @Test public void shouldThrowWhenParsingNonNumericNode() throws Exception { exception.expect(JWTDecodeException.class); - exception.expectMessage("The claim 'key' contained an unexpected value."); + exception.expectMessage("The claim 'key' contained a non-numeric date value."); Map tree = new HashMap<>(); TextNode node = new TextNode("123456789"); From 00132e97fd7e4cab8a7c5f4fc943168edebbce3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Mon, 26 Feb 2018 15:02:11 +0100 Subject: [PATCH 066/355] Bump Jackson dependency, add OWASP dependency-check plugin --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index fb782d71..377d4c85 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.2' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5' compile 'commons-codec:commons-codec:1.11' testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' testCompile 'junit:junit:4.12' From 9b3445375f7f998984d84f4107def619e2656584 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 13 Jun 2018 17:55:48 -0300 Subject: [PATCH 067/355] use jackson-databind 2.9.6 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 377d4c85..5e8f7059 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6' compile 'commons-codec:commons-codec:1.11' testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' testCompile 'junit:junit:4.12' From e7ff985d5199575c9594d2b1d627b096e6f8a87d Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 13 Jun 2018 17:47:10 -0300 Subject: [PATCH 068/355] bump dependencies. Fix security issue: Deserialization of Untrusted Data --- build.gradle | 3 +-- lib/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index b18f7596..f1dc45a3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1' } } diff --git a/lib/build.gradle b/lib/build.gradle index 5e8f7059..7da318e5 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -36,11 +36,11 @@ compileJava { dependencies { compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6' compile 'commons-codec:commons-codec:1.11' - testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' + testCompile 'org.bouncycastle:bcprov-jdk15on:1.59' testCompile 'junit:junit:4.12' testCompile 'net.jodah:concurrentunit:0.4.3' testCompile 'org.hamcrest:java-hamcrest:2.0.0.0' - testCompile 'org.mockito:mockito-core:2.11.0' + testCompile 'org.mockito:mockito-core:2.18.3' } jacocoTestReport { From 8930e462b5fafd7e90dab35f3d7a0338c783e1fc Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 13 Jun 2018 18:13:05 -0300 Subject: [PATCH 069/355] Release 3.4.0 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb8210b..ba66c602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [3.4.0](https://github.com/auth0/java-jwt/tree/3.4.0) (2018-06-13) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.3.0...3.4.0) + +**Changed** +- Fix for issue #236 - refactored HMACAlgorithm so that it doesn't throw an UnsupportedEncodingException [\#242](https://github.com/auth0/java-jwt/pull/242) ([obecker](https://github.com/obecker)) +- Throw JWTDecodeException when date claim format is invalid [\#241](https://github.com/auth0/java-jwt/pull/241) ([lbalmaceda](https://github.com/lbalmaceda)) + +**Security** +- Bump Jackson dependency [\#244](https://github.com/auth0/java-jwt/pull/244) ([skjolber](https://github.com/skjolber)) + ## [3.3.0](https://github.com/auth0/java-jwt/tree/3.3.0) (2017-11-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.2.0...3.3.0) **Closed issues** diff --git a/README.md b/README.md index e98f1f79..39045fe9 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.3.0 + 3.4.0 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.3.0' +compile 'com.auth0:java-jwt:3.4.0' ``` ## Available Algorithms From 1578d706f2057f89c76cac2ed4d51e0f9136e69e Mon Sep 17 00:00:00 2001 From: Andreas Gebhardt Date: Mon, 30 Jul 2018 20:03:30 +0200 Subject: [PATCH 070/355] =?UTF-8?q?update=20link=20title=20for=20=C2=BBJSO?= =?UTF-8?q?N=20Web=20Token=C2=AB=20(#217)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update link title for »JSON Web Token« to corresponding RFC title. Also update link target to Internet Engineering Task Force (IETF). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39045fe9..4cd787e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt/v3.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) -A Java implementation of [JSON Web Tokens (draft-ietf-oauth-json-web-token-08)](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). +A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. From 1e3696fd7558e4adcc2d77194b1b7f2ead4754a8 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 14 Aug 2018 10:03:51 -0300 Subject: [PATCH 071/355] remove jdk7 build --- .circleci/config.yml | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 909f794c..f8394fc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,33 +1,10 @@ version: 2 jobs: - java_7_build: - docker: - - image: openjdk:7u121-jdk - steps: - - checkout - - run: chmod +x gradlew - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - # run tests! - - run: ./gradlew clean check jacocoTestReport --continue --console=plain - - run: - name: Upload Coverage - when: on_success - command: bash <(curl -s https://codecov.io/bash) - - save_cache: - paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} + build: environment: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - - java_8_build: docker: - image: openjdk:8-jdk steps: @@ -48,15 +25,4 @@ jobs: - save_cache: paths: - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb - -workflows: - version: 2 - build: - jobs: - - java_7_build - - java_8_build + key: v1-dependencies-{{ checksum "build.gradle" }} \ No newline at end of file From edeb590441f5400b6e7a11c29a5dd821edc9c617 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 14 Aug 2018 11:50:52 -0300 Subject: [PATCH 072/355] update codecov patch settings --- .codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 63e5785f..51f857af 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,7 +5,8 @@ coverage: status: patch: default: - if_no_uploads: error + if_no_uploads: success + if_ci_failed: error changes: true project: default: From de3b80d0002b18f6f3736c3703446b7fe5b95033 Mon Sep 17 00:00:00 2001 From: Quynh Anh Nguyen Date: Thu, 4 Oct 2018 16:26:35 +0800 Subject: [PATCH 073/355] Improve README for the Using KeyProvider example --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 4cd787e1..6e92f8aa 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,7 @@ By using a `KeyProvider` you can change in runtime the key used either to verify - `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. -The following snippet uses example classes showing how this would work: - +The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. ```java final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); @@ -105,9 +104,6 @@ Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs. ``` -> For simple key rotation using JWKs try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. - - ### Create and Sign a Token You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the `Algorithm` instance. From ab1433e2bc117e7ae68aaf9a72bb93796750ac05 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 24 Oct 2018 13:58:08 -0300 Subject: [PATCH 074/355] stop building CI for JDK 7 --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f8394fc2..9bb01639 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,10 +1,6 @@ version: 2 jobs: build: - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb docker: - image: openjdk:8-jdk steps: @@ -21,8 +17,12 @@ jobs: - run: name: Upload Coverage when: on_success - command: bash <(curl -s https://codecov.io/bash) + command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 - save_cache: paths: - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} \ No newline at end of file + key: v1-dependencies-{{ checksum "build.gradle" }} + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb From d88136ee45e1030e285b46c0bc73ea052ea669d4 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 24 Oct 2018 13:52:43 -0300 Subject: [PATCH 075/355] update jackson-databind dependency --- .gitignore | 1 + lib/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ab35b855..5ab9b34a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ Temporary Items ## Plugin-specific files: # IntelliJ +bin/ /out/ /lib/out/ diff --git a/lib/build.gradle b/lib/build.gradle index 7da318e5..d6787ed4 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7' compile 'commons-codec:commons-codec:1.11' testCompile 'org.bouncycastle:bcprov-jdk15on:1.59' testCompile 'junit:junit:4.12' From 09e5178f487330f3d00393405e5732befbc8522d Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 24 Oct 2018 14:06:41 -0300 Subject: [PATCH 076/355] Release 3.4.1 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba66c602..e4e454b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.4.1](https://github.com/auth0/java-jwt/tree/3.4.1) (2018-10-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.4.0...3.4.1) + +**Security** +- Update jackson-databind dependency [\#292](https://github.com/auth0/java-jwt/pull/292) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.4.0](https://github.com/auth0/java-jwt/tree/3.4.0) (2018-06-13) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.3.0...3.4.0) diff --git a/README.md b/README.md index 4cd787e1..53663e73 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.4.0 + 3.4.1 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.4.0' +compile 'com.auth0:java-jwt:3.4.1' ``` ## Available Algorithms From aa01becf8e6c034dcab58c042c95761d8f800b42 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 24 Oct 2018 14:25:38 -0300 Subject: [PATCH 077/355] use latest OSS plugin. bumps gradle version too --- build.gradle | 7 +- gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 56177 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 72 ++++++---- gradlew.bat | 14 +- lib/build.gradle | 62 ++++----- scripts/bintray.gradle | 55 -------- scripts/maven.gradle | 105 -------------- scripts/release.gradle | 170 ----------------------- 9 files changed, 79 insertions(+), 409 deletions(-) delete mode 100644 scripts/bintray.gradle delete mode 100644 scripts/maven.gradle delete mode 100644 scripts/release.gradle diff --git a/build.gradle b/build.gradle index f1dc45a3..c348b47a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,13 @@ buildscript { repositories { - jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1' + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4" + classpath "gradle.plugin.com.auth0.gradle:oss-library:0.8.0" } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..29953ea141f55e3b8fc691d31b5ca8816d89fa87 100644 GIT binary patch literal 56177 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 i;x|p)^~-yfPOPa3(|vBayXKzPVt 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ⅅ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 H-+%o-(+5aQ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04e285f3..d76b502e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradlew b/gradlew index 9d82f789..cccdd3d5 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730..e95643d6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/lib/build.gradle b/lib/build.gradle index d6787ed4..f8a85c50 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,30 +1,27 @@ -apply plugin: 'jacoco' -apply plugin: 'java' -apply from: '../scripts/release.gradle' -apply from: '../scripts/maven.gradle' -apply from: '../scripts/bintray.gradle' +apply plugin: "com.auth0.gradle.oss-library.java" +apply plugin: "jacoco" logger.lifecycle("Using version ${version} for ${group}.${name}") -auth0 { +oss { name "java jwt" - repo "java-jwt" + repository "java-jwt" + organization "auth0" description "Java implementation of JSON Web Token (JWT)" - url 'http://www.jwt.io' - developer { - id = "auth0" - name = "Auth0" - email = "oss@auth0.com" - } - developer { - id = "lbalmaceda" - name = "Luciano Balmaceda" - email = "luciano.balmaceda@auth0.com" - } - developer { - id = "hzalaz" - name = "Hernan Zalazar" - email = "hernan@auth0.com" + + developers { + auth0 { + displayName = "Auth0" + email = "oss@auth0.com" + } + lbalmaceda { + displayName = "Luciano Balmaceda" + email = "luciano.balmaceda@auth0.com" + } + hzalaz { + displayName = "Hernan Zalazar" + email = "hernan@auth0.com" + } } } @@ -34,13 +31,13 @@ compileJava { } dependencies { - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7' - compile 'commons-codec:commons-codec:1.11' - testCompile 'org.bouncycastle:bcprov-jdk15on:1.59' - testCompile 'junit:junit:4.12' - testCompile 'net.jodah:concurrentunit:0.4.3' - testCompile 'org.hamcrest:java-hamcrest:2.0.0.0' - testCompile 'org.mockito:mockito-core:2.18.3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.7' + implementation 'commons-codec:commons-codec:1.11' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.59' + testImplementation 'junit:junit:4.12' + testImplementation 'net.jodah:concurrentunit:0.4.3' + testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0' + testImplementation 'org.mockito:mockito-core:2.18.3' } jacocoTestReport { @@ -55,9 +52,4 @@ test { events "skipped", "failed", "standardError" exceptionFormat "short" } -} - -task clean(type: Delete) { - delete rootProject.buildDir - delete 'CHANGELOG.md.release' -} +} \ No newline at end of file diff --git a/scripts/bintray.gradle b/scripts/bintray.gradle deleted file mode 100644 index ba868123..00000000 --- a/scripts/bintray.gradle +++ /dev/null @@ -1,55 +0,0 @@ -def credentials = new Bintray(project); - -if (credentials.valid()) { - apply plugin: 'com.jfrog.bintray' - bintray { - user = credentials.user - key = credentials.key - publications = ['mavenJava'] - dryRun = project.version.endsWith("-SNAPSHOT") - publish = false - pkg { - repo = 'java' - name = 'java-jwt' - desc = 'Java implementation of JSON Web Token (JWT) ' - websiteUrl = 'https://github.com/auth0/java-jwt' - vcsUrl = 'scm:git@github.com:auth0/java-jwt.git' - licenses = ["MIT"] - userOrg = 'auth0' - publish = false - version { - gpg { - sign = true - passphrase = credentials.passphrasse - } - vcsTag = project.version - name = project.version - released = new Date() - } - } - } -} - -class Bintray { - String user - String key - String passphrasse - - Bintray(project) { - this.user = Bintray.value(project, 'BINTRAY_USER', 'bintray.user') - this.key = Bintray.value(project, 'BINTRAY_KEY', 'bintray.key') - this.passphrasse = Bintray.value(project, 'BINTRAY_PASSPHRASE', 'bintray.gpg.password') - } - - def valid() { - return this.user != null && this.key != null && this.passphrasse != null; - } - - private static def value(Project project, String env, String property) { - def value = System.getenv(env) - if (project.hasProperty(property)) { - value = project.getProperty(property); - } - return value - } -} \ No newline at end of file diff --git a/scripts/maven.gradle b/scripts/maven.gradle deleted file mode 100644 index a541b008..00000000 --- a/scripts/maven.gradle +++ /dev/null @@ -1,105 +0,0 @@ -apply plugin: Auth0OSS - -class Auth0OSS implements Plugin { - - void apply(Project target) { - target.extensions.create("auth0", Auth0Extension, target) - target.configure(target) { - apply plugin: 'maven-publish' - - target.task("sourcesJar", type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource - } - target.task("javadocJar", type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.getDestinationDir() - } - - artifacts { - archives sourcesJar, javadocJar - } - - publishing { - publications { - mavenJava(MavenPublication) { - from components.java - artifact sourcesJar - artifact javadocJar - groupId project.group - artifactId project.name - version project.version - } - } - } - - publishing.publications.all { - pom.withXml { - - def lib = project.extensions.auth0 - def root = asNode() - - root.appendNode('packaging', 'jar') - root.appendNode('name', lib.name) - root.appendNode('description', lib.description) - root.appendNode('url', lib.url) - - def developersNode = root.appendNode('developers') - project.extensions.auth0.developers.each { - def node = developersNode.appendNode('developer') - node.appendNode('id', it.id) - node.appendNode('name', it.name) - node.appendNode('email', it.email) - } - - def dependenciesNode = root.appendNode('dependencies') - - configurations.compile.allDependencies.each { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', it.group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - } - - def licenceNode = root.appendNode('licenses').appendNode('license') - licenceNode.appendNode('name', 'The MIT License (MIT)') - licenceNode.appendNode('url', "https://raw.githubusercontent.com/auth0/${lib.repo}/master/LICENSE") - licenceNode.appendNode('distribution', 'repo') - - def scmNode = root.appendNode('scm') - scmNode.appendNode('connection', "scm:git@github.com:auth0/${lib.repo}.git") - scmNode.appendNode('developerConnection', "scm:git@github.com:auth0/${lib.repo}.git") - scmNode.appendNode('url', "https://github.com/auth0/${lib.repo}") - } - } - } - } -} - -class Auth0Extension { - String name - String repo - String description - String url - List developers = [] - - private Project project - - Auth0Extension(project) { - this.project = project - } - - void developer(Closure developerClosure) { - def developer = project.configure(new Developer(), developerClosure) - developers.add(developer) - } -} - -class Developer { - String id - String name - String email -} - - - diff --git a/scripts/release.gradle b/scripts/release.gradle deleted file mode 100644 index da74e9b5..00000000 --- a/scripts/release.gradle +++ /dev/null @@ -1,170 +0,0 @@ -import java.text.SimpleDateFormat - -apply plugin: ReleasePlugin - -class Semver { - String version - def snapshot - - def getStringVersion() { - return snapshot ? "$version-SNAPSHOT" : version - } - - def nextPatch() { - def parts = version.split("\\.") - def patch = Integer.parseInt(parts[2]) + 1 - return "${parts[0]}.${parts[1]}.${patch}" - } - - def nextMinor() { - def parts = version.split("\\.") - def minor = Integer.parseInt(parts[1]) + 1 - return "${parts[0]}.${minor}.0" - } - -} - -class ChangeLogTask extends DefaultTask { - - def current - def next - - @TaskAction - def update() { - def repository = project.auth0.repo - def file = new File('CHANGELOG.md') - def output = new File('CHANGELOG.md.release') - output.newWriter().withWriter { writer -> - - file.eachLine { line, number -> - if (number == 0 && !line.startsWith('# Change Log')) { - throw new GradleException('Change Log file is not properly formatted') - } - - writer.println(line) - - if (number == 0 || number > 1) { - return - } - - def formatter = new SimpleDateFormat('yyyy-MM-dd') - writer.println() - writer.println("## [${next}](https://github.com/auth0/${repository}/tree/${next}) (${formatter.format(new Date())})") - writer.println("[Full Changelog](https://github.com/auth0/${repository}/compare/${current}...${next})") - def command = ["curl", "https://webtask.it.auth0.com/api/run/wt-hernan-auth0_com-0/oss-changelog.js?webtask_no_cache=1&repo=${repository}&milestone=${next}", "-f", "-s", "-H", "Accept: text/markdown"] - def content = command.execute() - content.consumeProcessOutputStream(writer) - if (content.waitFor() != 0) { - throw new GradleException("Failed to request changelog for version ${next}") - } - } - } - file.delete() - output.renameTo('CHANGELOG.md') - } -} - -class ReleaseTask extends DefaultTask { - - def tagName - - @TaskAction - def perform() { - def path = project.getRootProject().getProjectDir().path - project.exec { - commandLine 'git', 'add', 'README.md' - workingDir path - } - project.exec { - commandLine 'git', 'add', 'CHANGELOG.md' - workingDir path - } - project.exec { - commandLine 'git', 'commit', '-m', "Release ${tagName}" - workingDir path - } - project.exec { - commandLine 'git', 'tag', "${tagName}" - workingDir path - } - } -} - -class ReadmeTask extends DefaultTask { - - def current - def next - - final filename = 'README.md' - - @TaskAction - def update() { - def file = new File(filename) - def gradleUpdated = "compile '${project.group}:${project.name}:${next}'" - def oldSingleQuote = "compile '${project.group}:${project.name}:${current}'" - def oldDoubleQuote = "compile \"${project.group}:${project.name}:${current}\"" - def mavenUpdated = "${next}" - def mavenOld = "${current}" - def contents = file.getText('UTF-8') - contents = contents.replace(oldSingleQuote, gradleUpdated).replace(oldDoubleQuote, gradleUpdated).replace(mavenOld, mavenUpdated) - file.write(contents, 'UTF-8') - } - -} - -class ReleasePlugin implements Plugin { - void apply(Project target) { - def semver = current() - target.version = semver.stringVersion - def version = semver.version - def nextMinor = semver.nextMinor() - def nextPatch = semver.nextPatch() - target.task('changelogMinor', type: ChangeLogTask) { - current = version - next = nextMinor - } - target.task('changelogPatch', type: ChangeLogTask) { - current = version - next = nextPatch - } - target.task('readmeMinor', type: ReadmeTask, dependsOn: 'changelogMinor') { - current = version - next = nextMinor - } - target.task('readmePatch', type: ReadmeTask, dependsOn: 'changelogPatch') { - current = version - next = nextPatch - } - target.task('releaseMinor', type: ReleaseTask, dependsOn: 'readmeMinor') { - tagName = nextMinor - } - target.task('releasePatch', type: ReleaseTask, dependsOn: 'readmePatch') { - tagName = nextPatch - } - } - - static def current() { - def current = describeGit(false) - def snapshot = current == null - if (snapshot) { - current = describeGit(snapshot, '0.0.1') - } - return new Semver(snapshot: snapshot, version: current) - } - - static def describeGit(boolean snapshot, String defaultValue = null) { - def command = ['git', 'describe', '--tags', (snapshot ? '--abbrev=0' : '--exact-match')].execute() - def stdout = new ByteArrayOutputStream() - def errout = new ByteArrayOutputStream() - command.consumeProcessOutput(stdout, errout) - if (command.waitFor() != 0) { - Logging.getLogger(ReleasePlugin.class).debug(errout.toString()) - return defaultValue - } - if (stdout.toByteArray().length > 0) { - return stdout.toString().replace('\n', "") - } - - return defaultValue - } -} \ No newline at end of file From 571f0a2627d181e135bf1b96c7809b38a2009f11 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 24 Oct 2018 14:31:52 -0300 Subject: [PATCH 078/355] use new plugin sintax --- build.gradle | 12 ------------ lib/build.gradle | 7 +++++-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index c348b47a..bf6dfe4f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4" - classpath "gradle.plugin.com.auth0.gradle:oss-library:0.8.0" - } -} - allprojects { group = 'com.auth0' diff --git a/lib/build.gradle b/lib/build.gradle index f8a85c50..041b3472 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,8 @@ -apply plugin: "com.auth0.gradle.oss-library.java" -apply plugin: "jacoco" +plugins { + id "com.jfrog.bintray" version "1.8.4" + id "com.auth0.gradle.oss-library.java" version "0.8.0" + id "jacoco" +} logger.lifecycle("Using version ${version} for ${group}.${name}") From 92789bfaa8dd7d897a55af6290b08488706adf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Horstmann?= Date: Mon, 29 Oct 2018 14:49:18 +0100 Subject: [PATCH 079/355] Remove unnecessary cast between long/double and floor call Calculating `time / 1000 * 1000` already returns the correctly truncated result using long arithmetic --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6f513c7e..30099252 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -427,7 +427,7 @@ private void assertValidStringClaim(String claimName, String value, String expec private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { Date today = clock.getToday(); - today.setTime((long) Math.floor((today.getTime() / 1000) * 1000)); // truncate millis + today.setTime(today.getTime() / 1000 * 1000); // truncate millis if (shouldBeFuture) { assertDateIsFuture(date, leeway, today); } else { From 2d61d32011306c36cae11695872d77e3750f24d7 Mon Sep 17 00:00:00 2001 From: "veerasekharbab.golla" Date: Tue, 30 Oct 2018 14:21:07 +0530 Subject: [PATCH 080/355] Issue#254: iat validation will be done by default and it can be ignored with ignoreIssuedAt(). --- .../main/java/com/auth0/jwt/JWTVerifier.java | 27 +++++++++++-------- .../java/com/auth0/jwt/JWTCreatorTest.java | 1 + .../java/com/auth0/jwt/JWTVerifierTest.java | 27 +++++++++++++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6f513c7e..00f49921 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -43,6 +43,7 @@ public static class BaseVerification implements Verification { private final Algorithm algorithm; private final Map claims; private long defaultLeeway; + private boolean ignoreIssuedAt; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -150,6 +151,10 @@ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException return this; } + public void ignoreIssuedAt() { + this.ignoreIssuedAt = true; + } + /** * Require a specific JWT Id ("jti") claim. * @@ -316,17 +321,17 @@ private void assertNonNull(String name) { } } - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - } + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if (!ignoreIssuedAt && !claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } private void requireClaim(String name, Object value) { if (value == null) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index ab263bad..571be017 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -195,6 +195,7 @@ public void shouldAddIssuedAt() throws Exception { .withIssuedAt(new Date(1477592000)) .sign(Algorithm.HMAC256("secret")); + System.out.println(signed); assertThat(signed, is(notNullValue())); assertThat(TokenUtils.splitToken(signed)[1], is("eyJpYXQiOjE0Nzc1OTJ9")); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d5826c24..459fa37d 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -494,6 +494,33 @@ public void shouldValidateIssuedAtWithLeeway() throws Exception { assertThat(jwt, is(notNullValue())); } + // Issued At with future date + @Test (expected = InvalidClaimException.class) + public void shouldThrowOnFutureIssuedAt() throws Exception { + Clock clock = mock(Clock.class); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + + DecodedJWT jwt = verification.build(clock).verify(token); + assertThat(jwt, is(notNullValue())); + } + + // Issued At with future date and ignore flag + @Test + public void shouldNotVerifyIATOnIgnoreIssuedAt() throws Exception { + Clock clock = mock(Clock.class); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification.ignoreIssuedAt(); + + DecodedJWT jwt = verification.build(clock).verify(token); + assertThat(jwt, is(notNullValue())); + } + @Test public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { exception.expect(InvalidClaimException.class); From 15ab908ab1274391ddca74882a426ba862a8165e Mon Sep 17 00:00:00 2001 From: "veerasekharbab.golla" Date: Tue, 30 Oct 2018 14:32:55 +0530 Subject: [PATCH 081/355] ignoring the sysouts added while testing --- lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 571be017..ab263bad 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -195,7 +195,6 @@ public void shouldAddIssuedAt() throws Exception { .withIssuedAt(new Date(1477592000)) .sign(Algorithm.HMAC256("secret")); - System.out.println(signed); assertThat(signed, is(notNullValue())); assertThat(TokenUtils.splitToken(signed)[1], is("eyJpYXQiOjE0Nzc1OTJ9")); } From 0b02e98089828fe66e7d2bb4137de1977e6aa6d2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 16 Nov 2018 15:48:11 -0300 Subject: [PATCH 082/355] Add missing javadoc --- .../main/java/com/auth0/jwt/interfaces/JWTVerifier.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index 49f285aa..1159c1fb 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -2,6 +2,15 @@ import com.auth0.jwt.exceptions.JWTVerificationException; + public interface JWTVerifier { + + /** + * Performs the verification against the given Token + * + * @param token to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the verification steps fail + */ DecodedJWT verify(String token) throws JWTVerificationException; } From dc1a367319b53ac7053fbaf3ee837a3c61cfdd55 Mon Sep 17 00:00:00 2001 From: lostship <418181589@qq.com> Date: Tue, 25 Dec 2018 18:41:42 +0800 Subject: [PATCH 083/355] Update README.md Time Validation * The token can already be used. `"nbf" < TODAY` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49a5632e..51a968d3 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ If the token has an invalid signature or the Claim requirement is not met, a `JW The JWT token may include DateNumber fields that can be used to validate that: * The token was issued in a past date `"iat" < TODAY` * The token hasn't expired yet `"exp" > TODAY` and -* The token can already be used. `"nbf" > TODAY` +* The token can already be used. `"nbf" < TODAY` When verifying a token the time validation occurs automatically, resulting in a `JWTVerificationException` being throw when the values are invalid. If any of the previous fields are missing they won't be considered in this validation. From 7cfb8c5a73808c470d31e1cdc83a14c05f1580f5 Mon Sep 17 00:00:00 2001 From: Martin O'Connor Date: Fri, 28 Dec 2018 10:07:18 -0500 Subject: [PATCH 084/355] Verify a DecodedJWT. Allows verification of a DecodedJWT previously obtained by invoking JWT.decode(). This alleviates the user from having to recompute the values of the DecodedJWT in order to validate the token. Reopens #279 --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 15 +++++++++++++++ .../com/auth0/jwt/interfaces/JWTVerifier.java | 9 +++++++++ lib/src/test/java/com/auth0/jwt/JWTTest.java | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index afeed0eb..456fee11 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -351,6 +351,21 @@ private void requireClaim(String name, Object value) { @Override public DecodedJWT verify(String token) throws JWTVerificationException { DecodedJWT jwt = JWT.decode(token); + return verify(jwt); + } + + /** + * Perform the verification against the given decoded JWT, using any previous configured options. + * + * @param jwt to verify. + * @return a verified and decoded JWT. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header it's not equal to the one defined in the {@link JWTVerifier}. + * @throws SignatureVerificationException if the signature is invalid. + * @throws TokenExpiredException if the token has expired. + * @throws InvalidClaimException if a claim contained a different value than the expected one. + */ + @Override + public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { verifyAlgorithm(jwt, algorithm); algorithm.verify(jwt); verifyClaims(jwt, claims); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index 1159c1fb..140af8e6 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -13,4 +13,13 @@ public interface JWTVerifier { * @throws JWTVerificationException if any of the verification steps fail */ DecodedJWT verify(String token) throws JWTVerificationException; + + /** + * Performs the verification against the given decoded JWT + * + * @param jwt to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the verification steps fail + */ + DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException; } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 31df4494..579ea271 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -58,6 +58,18 @@ public void shouldGetStringToken() throws Exception { // Verify + @Test + public void shouldVerifyDecodedToken() throws Exception { + String token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow"; + DecodedJWT decodedJWT = JWT.decode(token); + RSAKey key = (RSAKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_RSA, "RSA"); + DecodedJWT jwt = JWT.require(Algorithm.RSA512(key)) + .build() + .verify(decodedJWT); + + assertThat(jwt, is(notNullValue())); + } + @Test public void shouldAcceptNoneAlgorithm() throws Exception { String token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9."; From 326d58274e8de747aca5b38596464b48b8d8e3aa Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 3 Jan 2019 11:29:15 -0300 Subject: [PATCH 085/355] bump jackson dependency --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 041b3472..c13635e4 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.7' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' implementation 'commons-codec:commons-codec:1.11' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.59' testImplementation 'junit:junit:4.12' From 24319b70eaf627982d41c129272d261abf37800c Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 3 Jan 2019 15:35:13 -0300 Subject: [PATCH 086/355] Release 3.5.0 --- CHANGELOG.md | 15 +++++++++++++++ README.md | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e454b9..6bce7fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [3.5.0](https://github.com/auth0/java-jwt/tree/3.5.0) (2019-01-03) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.4.1...3.5.0) + +**Added** +- Verify a DecodedJWT [\#308](https://github.com/auth0/java-jwt/pull/308) ([martinoconnor](https://github.com/martinoconnor)) + +**Changed** +- Add an interface for JWTVerifier. [\#205](https://github.com/auth0/java-jwt/pull/205) ([jebbench](https://github.com/jebbench)) + +**Fixed** +- Remove unnecessary cast between long/double and floor call [\#296](https://github.com/auth0/java-jwt/pull/296) ([jhorstmann](https://github.com/jhorstmann)) + +**Security** +- Bump jackson-databind to patch security issues [\#309](https://github.com/auth0/java-jwt/pull/309) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.4.1](https://github.com/auth0/java-jwt/tree/3.4.1) (2018-10-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.4.0...3.4.1) diff --git a/README.md b/README.md index 51a968d3..c9a08cd8 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.4.1 + 3.5.0 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:3.4.1' +implementation 'com.auth0:java-jwt:3.5.0' ``` ## Available Algorithms From c9b6f296870e2293930e02ebca0bd2cfa9a83556 Mon Sep 17 00:00:00 2001 From: "veerasekharbab.golla" Date: Fri, 11 Jan 2019 15:29:58 +0530 Subject: [PATCH 087/355] Updated the code with review comments --- .../main/java/com/auth0/jwt/JWTVerifier.java | 15 +++-- .../auth0/jwt/interfaces/Verification.java | 2 + .../java/com/auth0/jwt/JWTVerifierTest.java | 64 +++++++++---------- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 00f49921..320a39f1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -151,9 +151,13 @@ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException return this; } - public void ignoreIssuedAt() { - this.ignoreIssuedAt = true; - } + /** + * Call this method to skip the default issued_at verification + */ + public Verification ignoreIssuedAt() { + this.ignoreIssuedAt = true; + return this; + } /** * Require a specific JWT Id ("jti") claim. @@ -328,9 +332,12 @@ private void addLeewayToDateClaims() { if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); } - if (!ignoreIssuedAt && !claims.containsKey(PublicClaims.ISSUED_AT)) { + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { claims.put(PublicClaims.ISSUED_AT, defaultLeeway); } + if(ignoreIssuedAt) { + claims.remove(PublicClaims.ISSUED_AT); + } } private void requireClaim(String name, Object value) { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 465ebe5d..a38468ab 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -37,5 +37,7 @@ public interface Verification { Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; + Verification ignoreIssuedAt(); + JWTVerifier build(); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 459fa37d..d7bcdd2c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -478,48 +478,32 @@ public void shouldThrowOnNegativeNotBeforeLeeway() throws Exception { .acceptNotBefore(-1); } - // Issued At - @Test - public void shouldValidateIssuedAtWithLeeway() throws Exception { +// Issued At with future date + @Test (expected = InvalidClaimException.class) + public void shouldThrowOnFutureIssuedAt() throws Exception { Clock clock = mock(Clock.class); when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) - .acceptIssuedAt(2); - DecodedJWT jwt = verification - .build(clock) - .verify(token); + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification.build(clock).verify(token); assertThat(jwt, is(notNullValue())); } - // Issued At with future date - @Test (expected = InvalidClaimException.class) - public void shouldThrowOnFutureIssuedAt() throws Exception { - Clock clock = mock(Clock.class); - when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); - - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - - DecodedJWT jwt = verification.build(clock).verify(token); - assertThat(jwt, is(notNullValue())); - } - - // Issued At with future date and ignore flag - @Test - public void shouldNotVerifyIATOnIgnoreIssuedAt() throws Exception { - Clock clock = mock(Clock.class); - when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + // Issued At with future date and ignore flag + @Test + public void shouldSkipIssuedAtVerificationWhenFlagIsPassed() throws Exception { + Clock clock = mock(Clock.class); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification.ignoreIssuedAt(); + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification.ignoreIssuedAt(); - DecodedJWT jwt = verification.build(clock).verify(token); - assertThat(jwt, is(notNullValue())); - } + DecodedJWT jwt = verification.build(clock).verify(token); + assertThat(jwt, is(notNullValue())); + } @Test public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { @@ -535,6 +519,20 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { .verify(token); } + @Test + public void shouldOverrideAcceptIssuedAtWhenIgnoreIssuedAtFlagPassedAndSkipTheVerification() throws Exception { + Clock clock = mock(Clock.class); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification.acceptIssuedAt(20).ignoreIssuedAt() + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + @Test public void shouldValidateIssuedAtIfPresent() throws Exception { Clock clock = mock(Clock.class); From 89400adbbb8b80dad66ada35cb44eb4b18a5952c Mon Sep 17 00:00:00 2001 From: "veerasekharbab.golla" Date: Sat, 12 Jan 2019 07:51:10 +0530 Subject: [PATCH 088/355] Formatter method and moved the ignoreIssuedAt to appropriate place --- .../main/java/com/auth0/jwt/JWTVerifier.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 002ce6ad..26de4034 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -152,7 +152,7 @@ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException } /** - * Call this method to skip the default issued_at verification + * Skip the Issued At ("iat") date verification. By default, the verification is performed. */ public Verification ignoreIssuedAt() { this.ignoreIssuedAt = true; @@ -325,20 +325,21 @@ private void assertNonNull(String name) { } } - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); - } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); - } - if(ignoreIssuedAt) { - claims.remove(PublicClaims.ISSUED_AT); - } - } + private void addLeewayToDateClaims() { + if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { + claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + } + if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { + claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + if(ignoreIssuedAt) { + claims.remove(PublicClaims.ISSUED_AT); + return; + } + if (!claims.containsKey(PublicClaims.ISSUED_AT)) { + claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + } + } private void requireClaim(String name, Object value) { if (value == null) { From b4265aff17cc2ba234f9d64078b10c1a8206f416 Mon Sep 17 00:00:00 2001 From: Gabe Terrell Date: Mon, 21 Jan 2019 10:05:33 -0600 Subject: [PATCH 089/355] Bumping org.bouncycastle:bcprov-jdk15on version --- lib/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index c13635e4..7126e324 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -36,7 +36,7 @@ compileJava { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' implementation 'commons-codec:commons-codec:1.11' - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.59' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0' @@ -55,4 +55,4 @@ test { events "skipped", "failed", "standardError" exceptionFormat "short" } -} \ No newline at end of file +} From a55854fb00ef484dd1aeb78d0befa7a57e2768d0 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 24 Jan 2019 10:05:02 -0300 Subject: [PATCH 090/355] Release 3.6.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bce7fb8..9e0aef02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.6.0](https://github.com/auth0/java-jwt/tree/3.6.0) (2019-01-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.5.0...3.6.0) + +**Added** +- Allow to skip "issued at" validation [\#297](https://github.com/auth0/java-jwt/pull/297) ([complanboy2](https://github.com/complanboy2)) + ## [3.5.0](https://github.com/auth0/java-jwt/tree/3.5.0) (2019-01-03) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.4.1...3.5.0) diff --git a/README.md b/README.md index c9a08cd8..5b8c7e07 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.5.0 + 3.6.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.5.0' +implementation 'com.auth0:java-jwt:3.6.0' ``` ## Available Algorithms From 35b2ea7a4635eb874b553eff05259a6cf3f10f66 Mon Sep 17 00:00:00 2001 From: Maxim Balan Date: Thu, 24 Jan 2019 14:48:55 +0000 Subject: [PATCH 091/355] fixing JwtCreator to set the headers to the exisitng map instead of ovewriting it --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a0889876..9c295f8d 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -71,7 +71,7 @@ public static class Builder { * @return this same Builder instance. */ public Builder withHeader(Map headerClaims) { - this.headerClaims = new HashMap<>(headerClaims); + this.headerClaims.putAll(headerClaims); return this; } From 9d94214275bfdb8eaf1a171fac03bd4557b407ea Mon Sep 17 00:00:00 2001 From: Maxim Balan Date: Thu, 24 Jan 2019 16:18:59 +0000 Subject: [PATCH 092/355] adding fails safty against empty map and removing a header if it was provided with a null value in the map also added unit test for this behaviour --- .../main/java/com/auth0/jwt/JWTCreator.java | 14 +++++- .../java/com/auth0/jwt/JWTCreatorTest.java | 43 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 9c295f8d..b656d5f8 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -66,12 +66,24 @@ public static class Builder { /** * Add specific Claims to set as the Header. + * If provided map is null then nothing + * If provided map contains a header with null value then that header will be removed from the header claims * * @param headerClaims the values to use as Claims in the token's Header. * @return this same Builder instance. */ public Builder withHeader(Map headerClaims) { - this.headerClaims.putAll(headerClaims); + if (headerClaims == null) + return this; + + for (Map.Entry entry : headerClaims.entrySet()) { + if (entry.getValue() == null) { + this.headerClaims.remove(entry.getKey()); + } else { + this.headerClaims.put(entry.getKey(), entry.getValue()); + } + } + return this; } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index ab263bad..f2a54803 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -53,6 +53,49 @@ public void shouldAddHeaderClaim() throws Exception { assertThat(headerJson, JsonMatcher.hasEntry("asd", 123)); } + @Test + public void shouldReturnBuilderIfNullMapIsProvided() throws Exception { + String signed = JWTCreator.init() + .withHeader(null) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + } + + @Test + public void shouldOverwriteExistingIfHeadersMapContainsTheSameKey() throws Exception { + Map header = new HashMap(); + header.put("test", 456); + + String signed = JWTCreator.init() + .withClaim("test", 123) + .withHeader(header) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("test", 456)); + } + + @Test + public void shouldRemoveHeaderIfTheValueIsNull() throws Exception { + Map header = new HashMap(); + header.put("test", null); + header.put("test2", "isSet"); + + String signed = JWTCreator.init() + .withClaim("test", 123) + .withHeader(header) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("test", null)); + assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); + } + @Test public void shouldAddKeyId() throws Exception { String signed = JWTCreator.init() From 7e33f20e4c67815c8ae1d425fe27a6ef7d19fb50 Mon Sep 17 00:00:00 2001 From: Maxim Balan Date: Thu, 24 Jan 2019 16:20:13 +0000 Subject: [PATCH 093/355] fix method description --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index b656d5f8..10a90df6 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -66,7 +66,7 @@ public static class Builder { /** * Add specific Claims to set as the Header. - * If provided map is null then nothing + * If provided map is null then nothing is changed * If provided map contains a header with null value then that header will be removed from the header claims * * @param headerClaims the values to use as Claims in the token's Header. From 8ca949eebe0d186567be72343c6e548ade001bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Tue, 29 Jan 2019 17:52:54 +0100 Subject: [PATCH 094/355] Performance improvements (#255) Performance improvements * Handle PublicClaims of date types in switch default case * Fix crypto test helper - allow minus (-) in token regexp * Add @Deprecated annotation and unit tests for deprecated signature methods * Formatting, remove unused imports, prefer StandardCharsets over apache commons Charsets --- lib/src/main/java/com/auth0/jwt/JWT.java | 26 ++- .../main/java/com/auth0/jwt/JWTCreator.java | 5 +- .../main/java/com/auth0/jwt/JWTDecoder.java | 5 +- .../main/java/com/auth0/jwt/JWTVerifier.java | 5 +- .../com/auth0/jwt/algorithms/Algorithm.java | 24 +++ .../auth0/jwt/algorithms/CryptoHelper.java | 175 ++++++++++++++++ .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 19 +- .../auth0/jwt/algorithms/HMACAlgorithm.java | 14 +- .../auth0/jwt/algorithms/NoneAlgorithm.java | 6 + .../auth0/jwt/algorithms/RSAAlgorithm.java | 17 +- .../java/com/auth0/jwt/impl/BasicHeader.java | 9 +- .../auth0/jwt/impl/HeaderDeserializer.java | 13 +- .../java/com/auth0/jwt/impl/JWTParser.java | 46 +++-- .../com/auth0/jwt/impl/JsonNodeClaim.java | 26 ++- .../auth0/jwt/impl/PayloadDeserializer.java | 17 +- .../java/com/auth0/jwt/impl/PayloadImpl.java | 13 +- .../com/auth0/jwt/impl/PayloadSerializer.java | 31 +-- .../java/com/auth0/jwt/interfaces/Claim.java | 7 +- lib/src/test/java/com/auth0/jwt/JWTTest.java | 18 ++ .../auth0/jwt/algorithms/AlgorithmTest.java | 27 +++ .../jwt/algorithms/CryptoTestHelper.java | 36 ++++ .../jwt/algorithms/ECDSAAlgorithmTest.java | 195 ++++++++++-------- .../ECDSABouncyCastleProviderTests.java | 133 +++++------- .../jwt/algorithms/HMACAlgorithmTest.java | 92 ++++----- .../jwt/algorithms/RSAAlgorithmTest.java | 162 ++++++++------- .../com/auth0/jwt/impl/BasicHeaderTest.java | 30 +-- .../jwt/impl/HeaderDeserializerTest.java | 7 +- .../com/auth0/jwt/impl/JWTParserTest.java | 39 ++-- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 15 +- .../jwt/impl/PayloadDeserializerTest.java | 2 +- .../com/auth0/jwt/impl/PayloadImplTest.java | 31 ++- 31 files changed, 823 insertions(+), 422 deletions(-) create mode 100644 lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index f8e5cfc3..ca05deff 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -2,11 +2,35 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.impl.JWTParser; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; @SuppressWarnings("WeakerAccess") -public abstract class JWT { +public class JWT { + + private final JWTParser parser; + + /** + * Constructs a new instance of the JWT library. Use this if you need to decode many JWT + * tokens on the fly and do not wish to instantiate a new parser for each invocation. + */ + public JWT() { + parser = new JWTParser(); + } + + /** + * Decode a given Json Web Token. + *

+ * Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. + * + * @param token with jwt format as string. + * @return a decoded JWT. + * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. + */ + public DecodedJWT decodeJwt(String token) throws JWTDecodeException { + return new JWTDecoder(parser, token); + } /** * Decode a given Json Web Token. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a0889876..75d255ef 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -329,11 +329,10 @@ private void addClaim(String name, Object value) { private String sign() throws SignatureGenerationException { String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8)); String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8)); - String content = String.format("%s.%s", header, payload); - byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); String signature = Base64.encodeBase64URLSafeString((signatureBytes)); - return String.format("%s.%s", content, signature); + return String.format("%s.%s.%s", header, payload, signature); } } diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 7921c128..34503f43 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -24,8 +24,11 @@ final class JWTDecoder implements DecodedJWT { private final Payload payload; JWTDecoder(String jwt) throws JWTDecodeException { + this(new JWTParser(), jwt); + } + + JWTDecoder(JWTParser converter, String jwt) throws JWTDecodeException { parts = TokenUtils.splitToken(jwt); - final JWTParser converter = new JWTParser(); String headerJson; String payloadJson; try { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 26de4034..6eea86bb 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -2,6 +2,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; +import com.auth0.jwt.impl.JWTParser; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; @@ -18,11 +19,13 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; final Map claims; private final Clock clock; + private final JWTParser parser; JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { this.algorithm = algorithm; this.claims = Collections.unmodifiableMap(claims); this.clock = clock; + this.parser = new JWTParser(); } /** @@ -363,7 +366,7 @@ private void requireClaim(String name, Object value) { */ @Override public DecodedJWT verify(String token) throws JWTVerificationException { - DecodedJWT jwt = JWT.decode(token); + DecodedJWT jwt = new JWTDecoder(parser, token); return verify(jwt); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 3cc66d07..24ad025b 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -6,6 +6,7 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; +import java.io.ByteArrayOutputStream; import java.security.interfaces.*; /** @@ -361,12 +362,35 @@ public String toString() { */ public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException; + /** + * Sign the given content using this Algorithm instance. + * + * @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature. + * @param payloadBytes an array of bytes representing the base64 encoded payload content to be verified against the signature. + * @return the signature in a base64 encoded array of bytes + * @throws SignatureGenerationException if the Key is invalid. + */ + public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { + // default implementation; keep around until sign(byte[]) method is removed + byte[] contentBytes = new byte[headerBytes.length + 1 + payloadBytes.length]; + + System.arraycopy(headerBytes, 0, contentBytes, 0, headerBytes.length); + contentBytes[headerBytes.length] = (byte)'.'; + System.arraycopy(payloadBytes, 0, contentBytes, headerBytes.length + 1, payloadBytes.length); + + return sign(contentBytes); + } + /** * Sign the given content using this Algorithm instance. * * @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. + * @deprecated Please use the {@linkplain #sign(byte[], byte[])} method instead. */ + + @Deprecated public abstract byte[] sign(byte[] contentBytes) throws SignatureGenerationException; + } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java index 08af662c..93e8f176 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java @@ -2,20 +2,181 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + +import java.nio.charset.StandardCharsets; import java.security.*; class CryptoHelper { + private static final byte JWT_PART_SEPARATOR = (byte)46; + + /** + * Verify signature for JWT header and payload. + * + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param header JWT header. + * @param payload JWT payload. + * @param signatureBytes JWT signature. + * @return true if signature is valid. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + */ + + boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { + return verifySignatureFor(algorithm, secretBytes, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + } + + /** + * Verify signature for JWT header and payload. + * + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param header JWT header. + * @param payload JWT payload. + * @param signatureBytes JWT signature. + * @return true if signature is valid. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + */ + + boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { + return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), signatureBytes); + } + + /** + * Create signature for JWT header and payload. + * + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. + * @return the signature bytes. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + */ + + byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException { + final Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(secretBytes, algorithm)); + mac.update(headerBytes); + mac.update(JWT_PART_SEPARATOR); + return mac.doFinal(payloadBytes); + } + + /** + * Verify signature for JWT header and payload. + * + * @param algorithm algorithm name. + * @param publicKey algorithm public key. + * @param header JWT header. + * @param payload JWT payload. + * @param signatureBytes JWT signature. + * @return true if signature is valid. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + */ + + boolean verifySignatureFor(String algorithm, PublicKey publicKey, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + } + + /** + * Verify signature for JWT header and payload using a public key. + * + * @param algorithm algorithm name. + * @param publicKey the public key to use for verification. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. + * @param signatureBytes JWT signature. + * @return true if signature is valid. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + */ + + boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + final Signature s = Signature.getInstance(algorithm); + s.initVerify(publicKey); + s.update(headerBytes); + s.update(JWT_PART_SEPARATOR); + s.update(payloadBytes); + return s.verify(signatureBytes); + } + + /** + * Create signature for JWT header and payload using a private key. + * + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. + * @return the signature bytes. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + */ + + byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + final Signature s = Signature.getInstance(algorithm); + s.initSign(privateKey); + s.update(headerBytes); + s.update(JWT_PART_SEPARATOR); + s.update(payloadBytes); + return s.sign(); + } + + /** + * Verify signature. + * + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param contentBytes the content to which the signature applies. + * @param signatureBytes JWT signature. + * @return true if signature is valid. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @deprecated rather use corresponding method which takes header and payload as separate inputs + */ + + @Deprecated boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes); } + /** + * Create signature. + * + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param contentBytes the content to be signed. + * @return the signature bytes. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @deprecated rather use corresponding method which takes header and payload as separate inputs + */ + + @Deprecated byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException { final Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(secretBytes, algorithm)); return mac.doFinal(contentBytes); } + /** + * Verify signature using a public key. + * + * @param algorithm algorithm name. + * @param publicKey algorithm public key. + * @param contentBytes the content to which the signature applies. + * @param signatureBytes JWT signature. + * @return the signature bytes. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @deprecated rather use corresponding method which takes header and payload as separate inputs + */ + + @Deprecated boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initVerify(publicKey); @@ -23,6 +184,20 @@ boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] content return s.verify(signatureBytes); } + /** + * Create signature using a private key. + * + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. + * @param contentBytes the content to be signed. + * @return the signature bytes. + * @throws NoSuchAlgorithmException if the algorithm is not supported. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @deprecated rather use corresponding method which takes header and payload as separate inputs + */ + + @Deprecated byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initSign(privateKey); 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 08ff2885..12ddca70 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -6,7 +6,6 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import org.apache.commons.codec.binary.Base64; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -36,7 +35,6 @@ class ECDSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); try { @@ -44,7 +42,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, JOSEToDER(signatureBytes)); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); @@ -55,6 +53,21 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { } @Override + public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { + try { + ECPrivateKey privateKey = keyProvider.getPrivateKey(); + if (privateKey == null) { + throw new IllegalStateException("The given Private Key is null."); + } + byte[] signature = crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes); + return DERToJOSE(signature); + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + throw new SignatureGenerationException(this, e); + } + } + + @Override + @Deprecated public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { ECPrivateKey privateKey = keyProvider.getPrivateKey(); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 670928f8..5df23a83 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -42,11 +42,10 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); try { - boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } @@ -56,6 +55,16 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { } @Override + public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { + try { + return crypto.createSignatureFor(getDescription(), secret, headerBytes, payloadBytes); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new SignatureGenerationException(this, e); + } + } + + @Override + @Deprecated public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { return crypto.createSignatureFor(getDescription(), secret, contentBytes); @@ -63,5 +72,4 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { throw new SignatureGenerationException(this, e); } } - } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index 136cf886..f70b72b2 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -20,6 +20,12 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { } @Override + public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { + return new byte[0]; + } + + @Override + @Deprecated public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { return new byte[0]; } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index b423e159..15cce55a 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -34,7 +34,6 @@ class RSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8); byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); try { @@ -42,7 +41,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } @@ -51,6 +50,20 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { } } + @Override + @Deprecated + public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { + try { + RSAPrivateKey privateKey = keyProvider.getPrivateKey(); + if (privateKey == null) { + throw new IllegalStateException("The given Private Key is null."); + } + return crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes); + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + throw new SignatureGenerationException(this, e); + } + } + @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 277724fd..b1d81a11 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -3,6 +3,7 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Header; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; import java.util.Collections; import java.util.HashMap; @@ -19,13 +20,15 @@ class BasicHeader implements Header { private final String contentType; private final String keyId; private final Map tree; - - BasicHeader(String algorithm, String type, String contentType, String keyId, Map tree) { + private final ObjectReader objectReader; + + BasicHeader(String algorithm, String type, String contentType, String keyId, Map tree, ObjectReader objectReader) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; this.keyId = keyId; this.tree = Collections.unmodifiableMap(tree == null ? new HashMap() : tree); + this.objectReader = objectReader; } Map getTree() { @@ -54,6 +57,6 @@ public String getKeyId() { @Override public Claim getHeaderClaim(String name) { - return extractClaim(name, tree); + return extractClaim(name, tree, objectReader); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index d42db420..cea2944a 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; @@ -12,12 +13,16 @@ class HeaderDeserializer extends StdDeserializer { - HeaderDeserializer() { - this(null); + private final ObjectReader objectReader; + + HeaderDeserializer(ObjectReader objectReader) { + this(null, objectReader); } - private HeaderDeserializer(Class vc) { + private HeaderDeserializer(Class vc, ObjectReader objectReader) { super(vc); + + this.objectReader = objectReader; } @Override @@ -32,7 +37,7 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws String type = getString(tree, PublicClaims.TYPE); String contentType = getString(tree, PublicClaims.CONTENT_TYPE); String keyId = getString(tree, PublicClaims.KEY_ID); - return new BasicHeader(algorithm, type, contentType, keyId, tree); + return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); } String getString(Map tree, String claimName) { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index 45854785..84a8b867 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -6,13 +6,15 @@ import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import java.io.IOException; public class JWTParser implements JWTPartsParser { - private ObjectMapper mapper; + private final ObjectReader payloadReader; + private final ObjectReader headerReader; public JWTParser() { this(getDefaultObjectMapper()); @@ -20,23 +22,41 @@ public JWTParser() { JWTParser(ObjectMapper mapper) { addDeserializers(mapper); - this.mapper = mapper; + this.payloadReader = mapper.readerFor(Payload.class); + this.headerReader = mapper.readerFor(Header.class); } @Override public Payload parsePayload(String json) throws JWTDecodeException { - return convertFromJSON(json, Payload.class); + if (json == null) { + throw decodeException(); + } + + try { + return payloadReader.readValue(json); + } catch (IOException e) { + throw decodeException(json); + } } @Override public Header parseHeader(String json) throws JWTDecodeException { - return convertFromJSON(json, Header.class); + if (json == null) { + throw decodeException(); + } + + try { + return headerReader.readValue(json); + } catch (IOException e) { + throw decodeException(json); + } } private void addDeserializers(ObjectMapper mapper) { SimpleModule module = new SimpleModule(); - module.addDeserializer(Payload.class, new PayloadDeserializer()); - module.addDeserializer(Header.class, new HeaderDeserializer()); + ObjectReader reader = mapper.reader(); + module.addDeserializer(Payload.class, new PayloadDeserializer(reader)); + module.addDeserializer(Header.class, new HeaderDeserializer(reader)); mapper.registerModule(module); } @@ -47,19 +67,11 @@ static ObjectMapper getDefaultObjectMapper() { return mapper; } - @SuppressWarnings("WeakerAccess") - T convertFromJSON(String json, Class tClazz) throws JWTDecodeException { - if (json == null) { - throw exceptionForInvalidJson(null); - } - try { - return mapper.readValue(json, tClazz); - } catch (IOException e) { - throw exceptionForInvalidJson(json); - } + private static JWTDecodeException decodeException() { + return decodeException(null); } - private JWTDecodeException exceptionForInvalidJson(String json) { + private static JWTDecodeException decodeException(String json) { return new JWTDecodeException(String.format("The string '%s' doesn't have a valid JSON format.", json)); } } 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 98e76a21..4e2aef63 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; import java.lang.reflect.Array; @@ -20,10 +21,12 @@ */ class JsonNodeClaim implements Claim { + private final ObjectReader objectReader; private final JsonNode data; - private JsonNodeClaim(JsonNode node) { + private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { this.data = node; + this.objectReader = objectReader; } @Override @@ -70,7 +73,7 @@ public T[] asArray(Class tClazz) throws JWTDecodeException { T[] arr = (T[]) Array.newInstance(tClazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = getObjectMapper().treeToValue(data.get(i), tClazz); + arr[i] = objectReader.treeToValue(data.get(i), tClazz); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); } @@ -87,7 +90,7 @@ public List asList(Class tClazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(getObjectMapper().treeToValue(data.get(i), tClazz)); + list.add(objectReader.treeToValue(data.get(i), tClazz)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); } @@ -104,8 +107,7 @@ public Map asMap() throws JWTDecodeException { try { TypeReference> mapType = new TypeReference>() { }; - ObjectMapper thisMapper = getObjectMapper(); - JsonParser thisParser = thisMapper.treeAsTokens(data); + JsonParser thisParser = objectReader.treeAsTokens(data); return thisParser.readValueAs(mapType); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to Map", e); @@ -115,7 +117,7 @@ public Map asMap() throws JWTDecodeException { @Override public T as(Class tClazz) throws JWTDecodeException { try { - return getObjectMapper().treeAsTokens(data).readValueAs(tClazz); + return objectReader.treeAsTokens(data).readValueAs(tClazz); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); } @@ -133,9 +135,9 @@ public boolean isNull() { * @param tree the JsonNode tree to search the Claim in. * @return a valid non-null Claim. */ - static Claim extractClaim(String claimName, Map tree) { + static Claim extractClaim(String claimName, Map tree, ObjectReader objectReader) { JsonNode node = tree.get(claimName); - return claimFromNode(node); + return claimFromNode(node, objectReader); } /** @@ -144,15 +146,11 @@ static Claim extractClaim(String claimName, Map tree) { * @param node the JsonNode to convert into a Claim. * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ - static Claim claimFromNode(JsonNode node) { + static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { if (node == null || node.isNull() || node.isMissingNode()) { return new NullClaim(); } - return new JsonNodeClaim(node); + return new JsonNodeClaim(node, objectReader); } - //Visible for testing - ObjectMapper getObjectMapper() { - return new ObjectMapper(); - } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 43047bfc..0c9de6c7 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; @@ -15,12 +15,16 @@ class PayloadDeserializer extends StdDeserializer { - PayloadDeserializer() { - this(null); + private final ObjectReader objectReader; + + PayloadDeserializer(ObjectReader reader) { + this(null, reader); } - private PayloadDeserializer(Class vc) { + private PayloadDeserializer(Class vc, ObjectReader reader) { super(vc); + + this.objectReader = reader; } @Override @@ -39,7 +43,7 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE Date issuedAt = getDateFromSeconds(tree, PublicClaims.ISSUED_AT); String jwtId = getString(tree, PublicClaims.JWT_ID); - return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree); + return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); } List getStringOrArray(Map tree, String claimName) throws JWTDecodeException { @@ -51,11 +55,10 @@ List getStringOrArray(Map tree, String claimName) thro return Collections.singletonList(node.asText()); } - ObjectMapper mapper = new ObjectMapper(); List list = new ArrayList<>(node.size()); for (int i = 0; i < node.size(); i++) { try { - list.add(mapper.treeToValue(node.get(i), String.class)); + list.add(objectReader.treeToValue(node.get(i), String.class)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to String", e); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index dced3062..d3b1dc0c 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -3,6 +3,7 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; import java.util.*; @@ -20,8 +21,9 @@ class PayloadImpl implements Payload { private final Date issuedAt; private final String jwtId; private final Map tree; + private final ObjectReader objectReader; - PayloadImpl(String issuer, String subject, List audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map tree) { + PayloadImpl(String issuer, String subject, List audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map tree, ObjectReader objectReader) { this.issuer = issuer; this.subject = subject; this.audience = audience; @@ -29,7 +31,8 @@ class PayloadImpl implements Payload { this.notBefore = notBefore; this.issuedAt = issuedAt; this.jwtId = jwtId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap() : tree); + this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); + this.objectReader = objectReader; } Map getTree() { @@ -73,14 +76,14 @@ public String getId() { @Override public Claim getClaim(String name) { - return extractClaim(name, tree); + return extractClaim(name, tree, objectReader); } @Override public Map getClaims() { - Map claims = new HashMap<>(); + Map claims = new HashMap<>(tree.size() * 2); for (String name : tree.keySet()) { - claims.put(name, extractClaim(name, tree)); + claims.put(name, extractClaim(name, tree, objectReader)); } return Collections.unmodifiableMap(claims); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 29f287eb..dd0d2f42 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.util.Date; -import java.util.HashMap; import java.util.Map; public class PayloadSerializer extends StdSerializer { @@ -21,37 +20,41 @@ private PayloadSerializer(Class t) { @Override public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider provider) throws IOException { - HashMap safePayload = new HashMap<>(); + + gen.writeStartObject(); for (Map.Entry e : holder.getClaims().entrySet()) { switch (e.getKey()) { case PublicClaims.AUDIENCE: if (e.getValue() instanceof String) { - safePayload.put(e.getKey(), e.getValue()); + gen.writeFieldName(e.getKey()); + gen.writeString((String)e.getValue()); break; } String[] audArray = (String[]) e.getValue(); if (audArray.length == 1) { - safePayload.put(e.getKey(), audArray[0]); + gen.writeFieldName(e.getKey()); + gen.writeString(audArray[0]); } else if (audArray.length > 1) { - safePayload.put(e.getKey(), audArray); + gen.writeFieldName(e.getKey()); + gen.writeStartArray(); + for(String aud : audArray) { + gen.writeString(aud); + } + gen.writeEndArray(); } break; - case PublicClaims.EXPIRES_AT: - case PublicClaims.ISSUED_AT: - case PublicClaims.NOT_BEFORE: - safePayload.put(e.getKey(), dateToSeconds((Date) e.getValue())); - break; default: - if (e.getValue() instanceof Date) { - safePayload.put(e.getKey(), dateToSeconds((Date) e.getValue())); + gen.writeFieldName(e.getKey()); + if (e.getValue() instanceof Date) { // true for EXPIRES_AT, ISSUED_AT, NOT_BEFORE + gen.writeNumber(dateToSeconds((Date) e.getValue())); } else { - safePayload.put(e.getKey(), e.getValue()); + gen.writeObject(e.getValue()); } break; } } - gen.writeObject(safePayload); + gen.writeEndObject(); } private long dateToSeconds(Date date) { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 893becd2..b3d41b1d 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -69,7 +69,8 @@ public interface Claim { /** * Get this Claim as an Array of type T. * If the value isn't an Array, null will be returned. - * + * @param type + * @param tClazz the type class * @return the value as an Array or null. * @throws JWTDecodeException if the values inside the Array can't be converted to a class T. */ @@ -79,6 +80,8 @@ public interface Claim { * Get this Claim as a List of type T. * If the value isn't an Array, null will be returned. * + * @param type + * @param tClazz the type class * @return the value as a List or null. * @throws JWTDecodeException if the values inside the List can't be converted to a class T. */ @@ -95,6 +98,8 @@ public interface Claim { /** * Get this Claim as a custom type T. * + * @param type + * @param tClazz the type class * @return the value as instance of T. * @throws JWTDecodeException if the value can't be converted to a class T. */ diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 579ea271..7cbe4b22 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -46,6 +46,15 @@ public void shouldDecodeAStringToken() throws Exception { assertThat(jwt, is(notNullValue())); } + @Test + public void shouldDecodeAStringTokenUsingInstance() throws Exception { + String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + JWT jwt = new JWT(); + DecodedJWT decodedJWT = jwt.decodeJwt(token); + + assertThat(decodedJWT, is(notNullValue())); + } + // getToken @Test public void shouldGetStringToken() throws Exception { @@ -55,6 +64,15 @@ public void shouldGetStringToken() throws Exception { assertThat(jwt.getToken(), is("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); } + // getToken + @Test + public void shouldGetStringTokenUsingInstance() throws Exception { + JWT jwt = new JWT(); + DecodedJWT decodedJWT = jwt.decodeJwt("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"); + assertThat(decodedJWT, is(notNullValue())); + assertThat(decodedJWT.getToken(), is(notNullValue())); + assertThat(decodedJWT.getToken(), is("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); + } // Verify diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index 6dcbbae9..f4f437f9 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -5,7 +5,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.interfaces.*; @@ -13,6 +15,8 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.withSettings; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; public class AlgorithmTest { @@ -546,4 +550,27 @@ public void shouldCreateNoneAlgorithm() throws Exception { assertThat(algorithm.getName(), is("none")); } + @Test + public void shouldForwardHeaderPayloadSignatureToSiblingSignMethodForBackwardsCompatibility() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + + ArgumentCaptor contentCaptor = ArgumentCaptor.forClass(byte[].class); + + byte[] header = new byte[]{0x00, 0x01, 0x02}; + byte[] payload = new byte[]{0x04, 0x05, 0x06}; + + byte[] signature = new byte[]{0x10, 0x11, 0x12}; + when(algorithm.sign(any(byte[].class), any(byte[].class))).thenCallRealMethod(); + when(algorithm.sign(contentCaptor.capture())).thenReturn(signature); + + byte[] sign = algorithm.sign(header, payload); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + bout.write(header); + bout.write('.'); + bout.write(payload); + + assertThat(sign, is(signature)); + assertThat(contentCaptor.getValue(), is(bout.toByteArray())); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java new file mode 100644 index 00000000..f977e42a --- /dev/null +++ b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java @@ -0,0 +1,36 @@ +package com.auth0.jwt.algorithms; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.codec.binary.Base64; + +public abstract class CryptoTestHelper { + + private static final Pattern authHeaderPattern = Pattern.compile("^([\\w-]+)\\.([\\w-]+)\\.([\\w-]+)"); + + public static String asJWT(Algorithm algorithm, String header, String payload) { + byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + return String.format("%s.%s.%s", header, payload, jwtSignature); + } + + public static void assertSignatureValue(String jwt, String expectedSignature) { + String jwtSignature = jwt.substring(jwt.lastIndexOf('.') + 1); + assertThat(jwtSignature, is(expectedSignature)); + } + + public static void assertSignaturePresent(String jwt) { + Matcher matcher = authHeaderPattern.matcher(jwt); + if (!matcher.find() || matcher.groupCount() < 3) { + fail("No signature present in " + jwt); + } + + assertThat(matcher.group(3), not(is(emptyString()))); + } +} 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 86bf3c0b..8968ed9f 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -4,6 +4,7 @@ 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.hamcrest.Matchers; import org.hamcrest.collection.IsIn; @@ -12,6 +13,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.ECKey; @@ -19,8 +21,11 @@ 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 com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; +import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; @@ -456,7 +461,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -474,7 +479,7 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -492,7 +497,7 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -509,30 +514,30 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception private static final String ES512Header = "eyJhbGciOiJFUzUxMiJ9"; private static final String auth0IssPayload = "eyJpc3MiOiJhdXRoMCJ9"; + private static final byte[] ES256HeaderBytes = ES256Header.getBytes(StandardCharsets.UTF_8); + private static final byte[] ES384HeaderBytes = ES384Header.getBytes(StandardCharsets.UTF_8); + private static final byte[] ES512HeaderBytes = ES512Header.getBytes(StandardCharsets.UTF_8); + private static final byte[] auth0IssPayloadBytes = auth0IssPayload.getBytes(StandardCharsets.UTF_8); + + @Test public void shouldDoECDSA256Signing() throws Exception { - Algorithm algorithmSign = Algorithm.ECDSA256((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + Algorithm algorithm = 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); + String jwt = asJWT(algorithm, ES256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + byte[] signatureBytes = algorithm.sign(ES256HeaderBytes, auth0IssPayloadBytes); String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); - String jwt = String.format("%s.%s", jwtContent, jwtSignature); + String jwt = String.format("%s.%s.%s", ES256Header, auth0IssPayload, jwtSignature); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -544,13 +549,10 @@ public void shouldDoECDSA256SigningWithProvidedPrivateKey() throws Exception { 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); + + String jwt = asJWT(algorithm, ES256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -564,7 +566,7 @@ public void shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA256(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -575,33 +577,25 @@ public void shouldFailOnECDSA256SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], 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); + String jwt = asJWT(algorithmSign, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + String jwt = asJWT(algorithm, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -613,13 +607,10 @@ public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { 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); + + String jwt = asJWT(algorithm, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -633,7 +624,7 @@ public void shouldFailOnECDSA384SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA384(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -644,33 +635,27 @@ public void shouldFailOnECDSA384SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], 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); + + String jwt = asJWT(algorithmSign, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + + String jwt = asJWT(algorithm, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -683,13 +668,10 @@ public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { 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); + + String jwt = asJWT(algorithm, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -703,7 +685,7 @@ public void shouldFailOnECDSA512SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA512(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -714,7 +696,7 @@ public void shouldFailOnECDSA512SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -724,14 +706,14 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256HeaderBytes, new byte[0]); } @Test @@ -741,14 +723,14 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256HeaderBytes, new byte[0]); } @Test @@ -758,14 +740,14 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256HeaderBytes, new byte[0]); } @Test @@ -795,7 +777,7 @@ public void shouldThrowOnDERSignatureConversionIfDoesNotStartWithCorrectSequence 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()); + byte[] signature = algorithm256.sign(content256.getBytes(), new byte[0]); signature[0] = (byte) 0x02; algorithm256.DERToJOSE(signature); } @@ -848,13 +830,11 @@ public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() th @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"; + String header256 = "eyJhbGciOiJFUzI1NiJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm256.sign(content256.getBytes()); - String signature256 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content256 + "." + signature256; + String jwt = asJWT(algorithm256, header256, body); algorithm256.verify(JWT.decode(jwt)); } } @@ -862,13 +842,11 @@ public void shouldSignAndVerifyWithECDSA256() throws Exception { @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"; + String header384 = "eyJhbGciOiJFUzM4NCJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm384.sign(content384.getBytes()); - String signature384 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content384 + "." + signature384; + String jwt = asJWT(algorithm384, header384, body); algorithm384.verify(JWT.decode(jwt)); } } @@ -876,13 +854,11 @@ public void shouldSignAndVerifyWithECDSA384() throws Exception { @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"; + String header512 = "eyJhbGciOiJFUzUxMiJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm512.sign(content512.getBytes()); - String signature512 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content512 + "." + signature512; + String jwt = asJWT(algorithm512, header512, body); algorithm512.verify(JWT.decode(jwt)); } } @@ -1164,4 +1140,53 @@ static void assertValidDERSignature(byte[] derSignature, int numberSize, boolean Assert.assertThat(derSignature.length, is(totalLength)); } + @Test + public void shouldBeEqualSignatureMethodDecodeResults() throws Exception { + // signatures are not deterministic in value, so instead of directly comparing the signatures, + // check that both sign(..) methods can be used to create a jwt which can be + // verified + Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + + String header = "eyJhbGciOiJFUzI1NiJ9"; + String payload = "eyJpc3MiOiJhdXRoMCJ9"; + + byte[] headerBytes = header.getBytes(StandardCharsets.UTF_8); + byte[] payloadBytes = payload.getBytes(StandardCharsets.UTF_8); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + bout.write(headerBytes); + bout.write('.'); + bout.write(payloadBytes); + + String jwtSignature1 = Base64.encodeBase64URLSafeString(algorithm.sign(bout.toByteArray())); + String jwt1 = String.format("%s.%s.%s", header, payload, jwtSignature1); + + algorithm.verify(JWT.decode(jwt1)); + + String jwtSignature2 = Base64.encodeBase64URLSafeString(algorithm.sign(headerBytes, payloadBytes)); + String jwt2 = String.format("%s.%s.%s", header, payload, jwtSignature2); + + algorithm.verify(JWT.decode(jwt2)); + } + + /** + * Test deprecated signing method error handling. + * + * @see {@linkplain #shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull} + * @throws Exception expected exception + */ + + @Test + public void shouldFailOnECDSA256SigningWithDeprecatedMethodWhenProvidedPrivateKeyIsNull() 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]); + } + } \ 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 index 4af1c4ec..452c0074 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -1,4 +1,5 @@ package com.auth0.jwt.algorithms; +import static com.auth0.jwt.algorithms.CryptoTestHelper.*; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.SignatureGenerationException; @@ -473,7 +474,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -491,7 +492,7 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -509,7 +510,7 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); @@ -530,26 +531,18 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception 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); + String jwt = asJWT(algorithmSign, ES256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + String jwt = asJWT(algorithm, ES256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -561,13 +554,10 @@ public void shouldDoECDSA256SigningWithProvidedPrivateKey() throws Exception { 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); + + String jwt = asJWT(algorithm, ES256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -581,7 +571,7 @@ public void shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA256(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -592,33 +582,25 @@ public void shouldFailOnECDSA256SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], 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); + String jwt = asJWT(algorithmSign, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + String jwt = asJWT(algorithm, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -630,13 +612,10 @@ public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { 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); + + String jwt = asJWT(algorithm, ES384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -650,7 +629,7 @@ public void shouldFailOnECDSA384SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA384(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -661,33 +640,26 @@ public void shouldFailOnECDSA384SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], 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); + + String jwt = asJWT(algorithmSign, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); 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); + String jwt = asJWT(algorithm, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -700,13 +672,9 @@ public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { 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); + String jwt = asJWT(algorithm, ES512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -720,7 +688,7 @@ public void shouldFailOnECDSA512SigningWhenProvidedPrivateKeyIsNull() throws Exc ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.ECDSA512(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -731,7 +699,7 @@ public void shouldFailOnECDSA512SigningWhenUsingPublicKey() throws Exception { 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]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -741,14 +709,14 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8), new byte[0]); } @Test @@ -758,14 +726,14 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8), new byte[0]); } @Test @@ -775,14 +743,14 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.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); - algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8), new byte[0]); } @Test @@ -812,7 +780,7 @@ public void shouldThrowOnDERSignatureConversionIfDoesNotStartWithCorrectSequence 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()); + byte[] signature = algorithm256.sign(content256.getBytes(), new byte[0]); signature[0] = (byte) 0x02; algorithm256.DERToJOSE(signature); } @@ -865,13 +833,11 @@ public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() th @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"; + String header256 = "eyJhbGciOiJFUzI1NiJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm256.sign(content256.getBytes()); - String signature256 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content256 + "." + signature256; + String jwt = asJWT(algorithm256, header256, body); algorithm256.verify(JWT.decode(jwt)); } } @@ -879,13 +845,11 @@ public void shouldSignAndVerifyWithECDSA256() throws Exception { @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"; + String header384 = "eyJhbGciOiJFUzM4NCJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm384.sign(content384.getBytes()); - String signature384 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content384 + "." + signature384; + String jwt = asJWT(algorithm384, header384, body); algorithm384.verify(JWT.decode(jwt)); } } @@ -893,13 +857,11 @@ public void shouldSignAndVerifyWithECDSA384() throws Exception { @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"; + String header512 = "eyJhbGciOiJFUzUxMiJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { - byte[] signature = algorithm512.sign(content512.getBytes()); - String signature512 = Base64.encodeBase64URLSafeString((signature)); - - String jwt = content512 + "." + signature512; + String jwt = asJWT(algorithm512, header512, body); algorithm512.verify(JWT.decode(jwt)); } } @@ -1054,4 +1016,5 @@ public void shouldDecodeECDSA512DER() throws Exception { assertValidJOSESignature(joseSignature, 66, true, true); } + } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 2d5fd301..b70c8633 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -4,16 +4,19 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; +import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; +import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignatureValue; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -127,7 +130,7 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); @@ -142,7 +145,7 @@ public void shouldThrowOnVerifyWhenTheSecretIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); @@ -161,15 +164,11 @@ public void shouldThrowOnVerifyWhenTheSecretIsInvalid() throws Exception { public void shouldDoHMAC256SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret".getBytes(StandardCharsets.UTF_8)); - String jwtContent = String.format("%s.%s", HS256Header, 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); + String jwt = asJWT(algorithm, HS256Header, auth0IssPayload); String expectedSignature = "s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -177,15 +176,11 @@ public void shouldDoHMAC256SigningWithBytes() throws Exception { public void shouldDoHMAC384SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC384("secret".getBytes(StandardCharsets.UTF_8)); - String jwtContent = String.format("%s.%s", HS384Header, 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); + String jwt = asJWT(algorithm, HS384Header, auth0IssPayload); String expectedSignature = "4-y2Gxz_foN0jAOFimmBPF7DWxf4AsjM20zxNkHg8Zah5Q64G42P9GfjmUp4Hldt"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -193,15 +188,11 @@ public void shouldDoHMAC384SigningWithBytes() throws Exception { public void shouldDoHMAC512SigningWithBytes() throws Exception { Algorithm algorithm = Algorithm.HMAC512("secret".getBytes(StandardCharsets.UTF_8)); - String jwtContent = String.format("%s.%s", HS512Header, 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); + String jwt = asJWT(algorithm, HS512Header, auth0IssPayload); String expectedSignature = "OXWyxmf-VcVo8viOiTFfLaEy6mrQqLEos5R82Xsx8mtFxQadJAQ1aVniIWN8qT2GNE_pMQPcdzk4x7Cqxsp1dw"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -209,15 +200,11 @@ public void shouldDoHMAC512SigningWithBytes() throws Exception { public void shouldDoHMAC256SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC256("secret"); - String jwtContent = String.format("%s.%s", HS256Header, 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); + String jwt = asJWT(algorithm, HS256Header, auth0IssPayload); String expectedSignature = "s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -225,15 +212,11 @@ public void shouldDoHMAC256SigningWithString() throws Exception { public void shouldDoHMAC384SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC384("secret"); - String jwtContent = String.format("%s.%s", HS384Header, 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); + String jwt = asJWT(algorithm, HS384Header, auth0IssPayload); String expectedSignature = "4-y2Gxz_foN0jAOFimmBPF7DWxf4AsjM20zxNkHg8Zah5Q64G42P9GfjmUp4Hldt"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -241,15 +224,11 @@ public void shouldDoHMAC384SigningWithString() throws Exception { public void shouldDoHMAC512SigningWithString() throws Exception { Algorithm algorithm = Algorithm.HMAC512("secret"); - String jwtContent = String.format("%s.%s", HS512Header, 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); + String jwt = asJWT(algorithm ,HS512Header, auth0IssPayload); String expectedSignature = "OXWyxmf-VcVo8viOiTFfLaEy6mrQqLEos5R82Xsx8mtFxQadJAQ1aVniIWN8qT2GNE_pMQPcdzk4x7Cqxsp1dw"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -260,11 +239,11 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(byte[].class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -274,11 +253,11 @@ public void shouldThrowOnSignWhenTheSecretIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(byte[].class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(byte[].class), any(byte[].class), any(byte[].class))) .thenThrow(InvalidKeyException.class); Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -286,4 +265,19 @@ public void shouldReturnNullSigningKeyId() throws Exception { assertThat(Algorithm.HMAC256("secret").getSigningKeyId(), is(nullValue())); } + @Test + public void shouldBeEqualSignatureMethodResults() throws Exception { + Algorithm algorithm = Algorithm.HMAC256("secret"); + + byte[] header = new byte[]{0x00, 0x01, 0x02}; + byte[] payload = new byte[]{0x04, 0x05, 0x06}; + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + bout.write(header); + bout.write('.'); + bout.write(payload); + + assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); + } + } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index 9025f9e4..b24365e3 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -4,12 +4,11 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.RSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.nio.charset.StandardCharsets; +import java.io.ByteArrayOutputStream; import java.security.*; import java.security.interfaces.RSAKey; import java.security.interfaces.RSAPrivateKey; @@ -24,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static com.auth0.jwt.algorithms.CryptoTestHelper.*; @SuppressWarnings("deprecation") public class RSAAlgorithmTest { @@ -213,9 +213,9 @@ public void shouldThrowWhenMacAlgorithmDoesNotExists() 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))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); @@ -233,7 +233,7 @@ public void shouldThrowWhenThePublicKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(InvalidKeyException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); @@ -251,7 +251,7 @@ public void shouldThrowWhenTheSignatureIsNotPrepared() throws Exception { exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(String.class), any(String.class), any(byte[].class))) .thenThrow(SignatureException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); @@ -274,15 +274,11 @@ public void shouldDoRSA256Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS256Header, 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); + String jwt = asJWT(algorithmSign, RS256Header, auth0IssPayload); String expectedSignature = "ZB-Tr0vLtnf8I9fhSdSjU6HZei5xLYZQ6nZqM5O6Va0W9PgAqgRT7ShI9CjeYulRXPHvVmSl5EQuYuXdBzM0-H_3p_Nsl6tSMy4EyX2kkhEm6T0HhvarTh8CG0PCjn5p6FP5ZxWwhLcmRN70ItP6Z5MMO4CcJh1JrNxR4Fi4xQgt-CK2aVDMFXd-Br5yQiLVx1CX83w28OD9wssW3Rdltl5e66vCef0Ql6Q5I5e5F0nqGYT989a9fkNgLIx2F8k_az5x07BY59FV2SZg59nSiY7TZNjP8ot11Ew7HKRfPXOdh9eKRUVdhcxzqDePhyzKabU8TG5FP0SiWH5qVPfAgw"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithmVerify.verify(JWT.decode(jwt)); } @@ -290,15 +286,11 @@ public void shouldDoRSA256Signing() throws Exception { public void shouldDoRSA256SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS256Header, 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); + String jwt = asJWT(algorithm, RS256Header, auth0IssPayload); String expectedSignature = "ZB-Tr0vLtnf8I9fhSdSjU6HZei5xLYZQ6nZqM5O6Va0W9PgAqgRT7ShI9CjeYulRXPHvVmSl5EQuYuXdBzM0-H_3p_Nsl6tSMy4EyX2kkhEm6T0HhvarTh8CG0PCjn5p6FP5ZxWwhLcmRN70ItP6Z5MMO4CcJh1JrNxR4Fi4xQgt-CK2aVDMFXd-Br5yQiLVx1CX83w28OD9wssW3Rdltl5e66vCef0Ql6Q5I5e5F0nqGYT989a9fkNgLIx2F8k_az5x07BY59FV2SZg59nSiY7TZNjP8ot11Ew7HKRfPXOdh9eKRUVdhcxzqDePhyzKabU8TG5FP0SiWH5qVPfAgw"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -310,13 +302,10 @@ public void shouldDoRSA256SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA256(provider); - String jwtContent = String.format("%s.%s", RS256Header, 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); + + String jwt = asJWT(algorithm, RS256Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -330,7 +319,7 @@ public void shouldFailOnRSA256SigningWhenProvidedPrivateKeyIsNull() throws Excep RSAKeyProvider provider = mock(RSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.RSA256(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -341,7 +330,7 @@ public void shouldFailOnRSA256SigningWhenUsingPublicKey() throws Exception { exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA256((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -349,15 +338,11 @@ public void shouldDoRSA384Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA384((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS384Header, 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); + String jwt = asJWT(algorithmSign, RS384Header, auth0IssPayload); String expectedSignature = "Jx1PaTBnjd_U56MNjifFcY7w9ImDbseg0y8Ijr2pSiA1_wzQb_wy9undaWfzR5YqdIAXvjS8AGuZUAzIoTG4KMgOgdVyYDz3l2jzj6wI-lgqfR5hTy1w1ruMUQ4_wobpdxAiJ4fEbg8Mi_GljOiCO-P1HilxKnpiOJZidR8MQGwTInsf71tOUkK4x5UsdmUueuZbaU-CL5kPnRfXmJj9CcdxZbD9oMlbo23dwkP5BNMrS2LwGGzc9C_-ypxrBIOVilG3WZxcSmuG86LjcZbnL6LBEfph5NmKBgQav147uipb_7umBEr1m2dYiB_9u606n3bcoo3rnsYYK_Xfi1GAEQ"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithmVerify.verify(JWT.decode(jwt)); } @@ -365,15 +350,11 @@ public void shouldDoRSA384Signing() throws Exception { public void shouldDoRSA384SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA384((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS384Header, 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); + String jwt = asJWT(algorithm, RS384Header, auth0IssPayload); String expectedSignature = "Jx1PaTBnjd_U56MNjifFcY7w9ImDbseg0y8Ijr2pSiA1_wzQb_wy9undaWfzR5YqdIAXvjS8AGuZUAzIoTG4KMgOgdVyYDz3l2jzj6wI-lgqfR5hTy1w1ruMUQ4_wobpdxAiJ4fEbg8Mi_GljOiCO-P1HilxKnpiOJZidR8MQGwTInsf71tOUkK4x5UsdmUueuZbaU-CL5kPnRfXmJj9CcdxZbD9oMlbo23dwkP5BNMrS2LwGGzc9C_-ypxrBIOVilG3WZxcSmuG86LjcZbnL6LBEfph5NmKBgQav147uipb_7umBEr1m2dYiB_9u606n3bcoo3rnsYYK_Xfi1GAEQ"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -385,13 +366,10 @@ public void shouldDoRSA384SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA384(provider); - String jwtContent = String.format("%s.%s", RS384Header, 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); + + String jwt = asJWT(algorithm, RS384Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -405,7 +383,7 @@ public void shouldFailOnRSA384SigningWhenProvidedPrivateKeyIsNull() throws Excep RSAKeyProvider provider = mock(RSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.RSA384(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -416,7 +394,7 @@ public void shouldFailOnRSA384SigningWhenUsingPublicKey() throws Exception { exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA384((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -424,15 +402,11 @@ public void shouldDoRSA512Signing() throws Exception { Algorithm algorithmSign = Algorithm.RSA512((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); Algorithm algorithmVerify = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS512Header, 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); + String jwt = asJWT(algorithmSign, RS512Header, auth0IssPayload); String expectedSignature = "THIPVYzNZ1Yo_dm0k1UELqV0txs3SzyMopCyHcLXOOdgYXF4MlGvBqu0CFvgSga72Sp5LpuC1Oesj40v_QDsp2GTGDeWnvvcv_eo-b0LPSpmT2h1Ibrmu-z70u2rKf28pkN-AJiMFqi8sit2kMIp1bwIVOovPvMTQKGFmova4Xwb3G526y_PeLlflW1h69hQTIVcI67ACEkAC-byjDnnYIklA-B4GWcggEoFwQRTdRjAUpifA6HOlvnBbZZlUd6KXwEydxVS-eh1odwPjB2_sfbyy5HnLsvNdaniiZQwX7QbwLNT4F72LctYdHHM1QCrID6bgfgYp9Ij9CRX__XDEA"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithmVerify.verify(JWT.decode(jwt)); } @@ -440,15 +414,11 @@ public void shouldDoRSA512Signing() throws Exception { public void shouldDoRSA512SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"), (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); - String jwtContent = String.format("%s.%s", RS512Header, 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); + String jwt = asJWT(algorithm, RS512Header, auth0IssPayload); String expectedSignature = "THIPVYzNZ1Yo_dm0k1UELqV0txs3SzyMopCyHcLXOOdgYXF4MlGvBqu0CFvgSga72Sp5LpuC1Oesj40v_QDsp2GTGDeWnvvcv_eo-b0LPSpmT2h1Ibrmu-z70u2rKf28pkN-AJiMFqi8sit2kMIp1bwIVOovPvMTQKGFmova4Xwb3G526y_PeLlflW1h69hQTIVcI67ACEkAC-byjDnnYIklA-B4GWcggEoFwQRTdRjAUpifA6HOlvnBbZZlUd6KXwEydxVS-eh1odwPjB2_sfbyy5HnLsvNdaniiZQwX7QbwLNT4F72LctYdHHM1QCrID6bgfgYp9Ij9CRX__XDEA"; - assertThat(signatureBytes, is(notNullValue())); - assertThat(jwtSignature, is(expectedSignature)); + assertSignaturePresent(jwt); + assertSignatureValue(jwt, expectedSignature); algorithm.verify(JWT.decode(jwt)); } @@ -460,13 +430,10 @@ public void shouldDoRSA512SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((RSAPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((RSAPublicKey) publicKey); Algorithm algorithm = Algorithm.RSA512(provider); - String jwtContent = String.format("%s.%s", RS512Header, 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); + + String jwt = asJWT(algorithm, RS512Header, auth0IssPayload); - assertThat(signatureBytes, is(notNullValue())); + assertSignaturePresent(jwt); algorithm.verify(JWT.decode(jwt)); } @@ -480,7 +447,7 @@ public void shouldFailOnRSA512SigningWhenProvidedPrivateKeyIsNull() throws Excep RSAKeyProvider provider = mock(RSAKeyProvider.class); when(provider.getPrivateKey()).thenReturn(null); Algorithm algorithm = Algorithm.RSA512(provider); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -491,7 +458,7 @@ public void shouldFailOnRSA512SigningWhenUsingPublicKey() throws Exception { exception.expectCause(hasMessage(is("The given Private Key is null."))); Algorithm algorithm = Algorithm.RSA512((RSAKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA")); - algorithm.sign(new byte[0]); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -501,14 +468,14 @@ public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Except exception.expectCause(isA(NoSuchAlgorithmException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class), any(byte[].class))) .thenThrow(NoSuchAlgorithmException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); - algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -518,14 +485,14 @@ public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { exception.expectCause(isA(InvalidKeyException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class), any(byte[].class))) .thenThrow(InvalidKeyException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); - algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -535,14 +502,14 @@ public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { exception.expectCause(isA(SignatureException.class)); CryptoHelper crypto = mock(CryptoHelper.class); - when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class), any(byte[].class))) .thenThrow(SignatureException.class); RSAPublicKey publicKey = mock(RSAPublicKey.class); RSAPrivateKey privateKey = mock(RSAPrivateKey.class); RSAKeyProvider provider = RSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new RSAAlgorithm(crypto, "some-alg", "some-algorithm", provider); - algorithm.sign(RS256Header.getBytes(StandardCharsets.UTF_8)); + algorithm.sign(new byte[0], new byte[0]); } @Test @@ -563,4 +530,43 @@ public void shouldReturnSigningKeyIdFromProvider() throws Exception { assertThat(algorithm.getSigningKeyId(), is("keyId")); } + + @Test + public void shouldBeEqualSignatureMethodResults() throws Exception { + RSAPrivateKey privateKey = (RSAPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA"); + RSAPublicKey publicKey = (RSAPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA"); + + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + + byte[] header = new byte[]{0x00, 0x01, 0x02}; + byte[] payload = new byte[]{0x04, 0x05, 0x06}; + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + bout.write(header); + bout.write('.'); + bout.write(payload); + + assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); + } + + /** + * Test deprecated signing method error handling. + * + * @see {@linkplain #shouldFailOnRSA256SigningWhenProvidedPrivateKeyIsNull} + * @throws Exception expected exception + */ + + @Test + public void shouldFailOnRSA256SigningWithDeprecatedMethodWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withRSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + RSAKeyProvider provider = mock(RSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.RSA256(provider); + algorithm.sign(new byte[0]); + } + } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java index d96baacf..c2d89556 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java @@ -1,6 +1,8 @@ package com.auth0.jwt.impl; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.TextNode; import org.hamcrest.collection.IsMapContaining; @@ -18,19 +20,21 @@ public class BasicHeaderTest { @Rule public ExpectedException exception = ExpectedException.none(); + + private ObjectReader objectReader = new ObjectMapper().reader(); @SuppressWarnings("Convert2Diamond") @Test public void shouldHaveUnmodifiableTreeWhenInstantiatedWithNonNullTree() throws Exception { exception.expect(UnsupportedOperationException.class); - BasicHeader header = new BasicHeader(null, null, null, null, new HashMap()); + BasicHeader header = new BasicHeader(null, null, null, null, new HashMap(), objectReader); header.getTree().put("something", null); } @Test public void shouldHaveUnmodifiableTreeWhenInstantiatedWithNullTree() throws Exception { exception.expect(UnsupportedOperationException.class); - BasicHeader header = new BasicHeader(null, null, null, null, null); + BasicHeader header = new BasicHeader(null, null, null, null, null, objectReader); header.getTree().put("something", null); } @@ -39,7 +43,7 @@ public void shouldHaveTree() throws Exception { HashMap map = new HashMap<>(); JsonNode node = NullNode.getInstance(); map.put("key", node); - BasicHeader header = new BasicHeader(null, null, null, null, map); + BasicHeader header = new BasicHeader(null, null, null, null, map, objectReader); assertThat(header.getTree(), is(notNullValue())); assertThat(header.getTree(), is(IsMapContaining.hasEntry("key", node))); @@ -47,7 +51,7 @@ public void shouldHaveTree() throws Exception { @Test public void shouldGetAlgorithm() throws Exception { - BasicHeader header = new BasicHeader("HS256", null, null, null, null); + BasicHeader header = new BasicHeader("HS256", null, null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getAlgorithm(), is(notNullValue())); @@ -56,7 +60,7 @@ public void shouldGetAlgorithm() throws Exception { @Test public void shouldGetNullAlgorithmIfMissing() throws Exception { - BasicHeader header = new BasicHeader(null, null, null, null, null); + BasicHeader header = new BasicHeader(null, null, null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getAlgorithm(), is(nullValue())); @@ -64,7 +68,7 @@ public void shouldGetNullAlgorithmIfMissing() throws Exception { @Test public void shouldGetType() throws Exception { - BasicHeader header = new BasicHeader(null, "jwt", null, null, null); + BasicHeader header = new BasicHeader(null, "jwt", null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getType(), is(notNullValue())); @@ -73,7 +77,7 @@ public void shouldGetType() throws Exception { @Test public void shouldGetNullTypeIfMissing() throws Exception { - BasicHeader header = new BasicHeader(null, null, null, null, null); + BasicHeader header = new BasicHeader(null, null, null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getType(), is(nullValue())); @@ -81,7 +85,7 @@ public void shouldGetNullTypeIfMissing() throws Exception { @Test public void shouldGetContentType() throws Exception { - BasicHeader header = new BasicHeader(null, null, "content", null, null); + BasicHeader header = new BasicHeader(null, null, "content", null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getContentType(), is(notNullValue())); @@ -90,7 +94,7 @@ public void shouldGetContentType() throws Exception { @Test public void shouldGetNullContentTypeIfMissing() throws Exception { - BasicHeader header = new BasicHeader(null, null, null, null, null); + BasicHeader header = new BasicHeader(null, null, null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getContentType(), is(nullValue())); @@ -98,7 +102,7 @@ public void shouldGetNullContentTypeIfMissing() throws Exception { @Test public void shouldGetKeyId() throws Exception { - BasicHeader header = new BasicHeader(null, null, null, "key", null); + BasicHeader header = new BasicHeader(null, null, null, "key", null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getKeyId(), is(notNullValue())); @@ -107,7 +111,7 @@ public void shouldGetKeyId() throws Exception { @Test public void shouldGetNullKeyIdIfMissing() throws Exception { - BasicHeader header = new BasicHeader(null, null, null, null, null); + BasicHeader header = new BasicHeader(null, null, null, null, null, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getKeyId(), is(nullValue())); @@ -117,7 +121,7 @@ public void shouldGetNullKeyIdIfMissing() throws Exception { public void shouldGetExtraClaim() throws Exception { Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); - BasicHeader header = new BasicHeader(null, null, null, null, tree); + BasicHeader header = new BasicHeader(null, null, null, null, tree, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getHeaderClaim("extraClaim"), is(instanceOf(JsonNodeClaim.class))); @@ -127,7 +131,7 @@ public void shouldGetExtraClaim() throws Exception { @Test public void shouldGetNotNullExtraClaimIfMissing() throws Exception { Map tree = new HashMap<>(); - BasicHeader header = new BasicHeader(null, null, null, null, tree); + BasicHeader header = new BasicHeader(null, null, null, null, tree, objectReader); assertThat(header, is(notNullValue())); assertThat(header.getHeaderClaim("missing"), is(notNullValue())); diff --git a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java index 3b6af385..dffcab50 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.TextNode; import org.junit.Before; @@ -33,11 +34,11 @@ public class HeaderDeserializerTest { @Rule public ExpectedException exception = ExpectedException.none(); private HeaderDeserializer deserializer; - + private ObjectReader objectReader = new ObjectMapper().reader(); @Before public void setUp() throws Exception { - deserializer = new HeaderDeserializer(); + deserializer = new HeaderDeserializer(objectReader); } @Test @@ -45,7 +46,7 @@ public void shouldThrowOnNullTree() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("Parsing the Header's JSON resulted on a Null map"); - JsonDeserializer deserializer = new HeaderDeserializer(); + JsonDeserializer deserializer = new HeaderDeserializer(objectReader); JsonParser parser = mock(JsonParser.class); ObjectCodec codec = mock(ObjectCodec.class); DeserializationContext context = mock(DeserializationContext.class); diff --git a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java index 1c990fcb..4f97b2bd 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; import org.junit.Before; import org.junit.Rule; @@ -17,6 +18,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class JWTParserTest { @@ -47,10 +49,12 @@ public void shouldAddDeserializers() throws Exception { @Test public void shouldParsePayload() throws Exception { ObjectMapper mapper = mock(ObjectMapper.class); + ObjectReader reader = mock(ObjectReader.class); + when(mapper.readerFor(Payload.class)).thenReturn(reader); JWTParser parser = new JWTParser(mapper); parser.parsePayload("{}"); - verify(mapper).readValue("{}", Payload.class); + verify(reader).readValue("{}"); } @Test @@ -65,10 +69,12 @@ public void shouldThrowOnInvalidPayload() throws Exception { @Test public void shouldParseHeader() throws Exception { ObjectMapper mapper = mock(ObjectMapper.class); + ObjectReader reader = mock(ObjectReader.class); + when(mapper.readerFor(Header.class)).thenReturn(reader); JWTParser parser = new JWTParser(mapper); parser.parseHeader("{}"); - verify(mapper).readValue("{}", Header.class); + verify(reader).readValue("{}"); } @Test @@ -81,27 +87,30 @@ public void shouldThrowOnInvalidHeader() throws Exception { } @Test - public void shouldConvertFromValidJSON() throws Exception { - String json = "\r\n { \r\n } \r\n"; - Object object = parser.convertFromJSON(json, Object.class); - assertThat(object, is(notNullValue())); + public void shouldThrowWhenConvertingHeaderIfNullJson() throws Exception { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The string 'null' doesn't have a valid JSON format."); + parser.parseHeader(null); + } + + @Test + public void shouldThrowWhenConvertingHeaderFromInvalidJson() throws Exception { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The string '}{' doesn't have a valid JSON format."); + parser.parseHeader("}{"); } @Test - public void shouldThrowWhenConvertingIfNullJson() throws Exception { + public void shouldThrowWhenConvertingPayloadIfNullJson() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("The string 'null' doesn't have a valid JSON format."); - String json = null; - Object object = parser.convertFromJSON(json, Object.class); - assertThat(object, is(nullValue())); + parser.parsePayload(null); } @Test - public void shouldThrowWhenConvertingFromInvalidJson() throws Exception { + public void shouldThrowWhenConvertingPayloadFromInvalidJson() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("The string '}{' doesn't have a valid JSON format."); - String json = "}{"; - Object object = parser.convertFromJSON(json, Object.class); - assertThat(object, is(nullValue())); + parser.parsePayload("}{"); } -} \ No newline at end of file +} 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 944860f9..1a82fcec 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; @@ -32,12 +33,15 @@ public class JsonNodeClaimTest { private ObjectMapper mapper; + private ObjectReader objectReader; + @Rule public ExpectedException exception = ExpectedException.none(); @Before public void setUp() throws Exception { mapper = getDefaultObjectMapper(); + objectReader = mapper.reader(); } @Test @@ -49,6 +53,10 @@ public void shouldGetBooleanValue() throws Exception { assertThat(claim.asBoolean(), is(true)); } + private Claim claimFromNode(JsonNode value) { + return JsonNodeClaim.claimFromNode(value, objectReader); + } + @Test public void shouldGetNullBooleanIfNotBooleanValue() throws Exception { JsonNode objectValue = mapper.valueToTree(new Object()); @@ -270,10 +278,11 @@ public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap( JsonNode value = mock(ObjectNode.class); when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT); - JsonNodeClaim claim = (JsonNodeClaim) claimFromNode(value); + ObjectReader mockedMapper = mock(ObjectReader.class); + + JsonNodeClaim claim = (JsonNodeClaim) JsonNodeClaim.claimFromNode(value, mockedMapper); 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); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 2e2cabed..9a82878e 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -36,7 +36,7 @@ public class PayloadDeserializerTest { @Before public void setUp() throws Exception { - deserializer = new PayloadDeserializer(); + deserializer = new PayloadDeserializer(new ObjectMapper().reader()); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index ca7562dc..56654427 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -2,6 +2,8 @@ import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.TextNode; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; @@ -16,6 +18,7 @@ import java.util.HashMap; import java.util.Map; +import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -29,21 +32,27 @@ public class PayloadImplTest { private Date notBefore; private Date issuedAt; + private ObjectMapper mapper; + private ObjectReader objectReader; + @Before public void setUp() throws Exception { + mapper = getDefaultObjectMapper(); + objectReader = mapper.reader(); + expiresAt = Mockito.mock(Date.class); notBefore = Mockito.mock(Date.class); issuedAt = Mockito.mock(Date.class); Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); - payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree); + payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectReader); } @SuppressWarnings("Convert2Diamond") @Test public void shouldHaveUnmodifiableTree() throws Exception { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap()); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap(), objectReader); payload.getTree().put("something", null); } @@ -55,7 +64,7 @@ public void shouldGetIssuer() throws Exception { @Test public void shouldGetNullIssuerIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuer(), is(nullValue())); } @@ -68,7 +77,7 @@ public void shouldGetSubject() throws Exception { @Test public void shouldGetNullSubjectIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getSubject(), is(nullValue())); } @@ -83,7 +92,7 @@ public void shouldGetAudience() throws Exception { @Test public void shouldGetNullAudienceIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getAudience(), is(nullValue())); } @@ -96,7 +105,7 @@ public void shouldGetExpiresAt() throws Exception { @Test public void shouldGetNullExpiresAtIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getExpiresAt(), is(nullValue())); } @@ -109,7 +118,7 @@ public void shouldGetNotBefore() throws Exception { @Test public void shouldGetNullNotBeforeIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getNotBefore(), is(nullValue())); } @@ -122,7 +131,7 @@ public void shouldGetIssuedAt() throws Exception { @Test public void shouldGetNullIssuedAtIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuedAt(), is(nullValue())); } @@ -135,7 +144,7 @@ public void shouldGetJWTId() throws Exception { @Test public void shouldGetNullJWTIdIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getId(), is(nullValue())); } @@ -149,7 +158,7 @@ public void shouldGetExtraClaim() throws Exception { @Test public void shouldGetNotNullExtraClaimIfMissing() throws Exception { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); assertThat(payload.getClaim("missing"), is(instanceOf(NullClaim.class))); @@ -160,7 +169,7 @@ public void shouldGetClaims() throws Exception { Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); tree.put("sub", new TextNode("auth0")); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectReader); assertThat(payload, is(notNullValue())); Map claims = payload.getClaims(); assertThat(claims, is(notNullValue())); From a682f9d8fe469adac0d557b5c15a154178d4a6f4 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 29 Jan 2019 14:19:35 -0300 Subject: [PATCH 095/355] Release 3.7.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0aef02..e951218b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.7.0](https://github.com/auth0/java-jwt/tree/3.7.0) (2019-01-29) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.6.0...3.7.0) + +**Added** +- Performance improvements [\#255](https://github.com/auth0/java-jwt/pull/255) ([skjolber](https://github.com/skjolber)) + ## [3.6.0](https://github.com/auth0/java-jwt/tree/3.6.0) (2019-01-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.5.0...3.6.0) diff --git a/README.md b/README.md index 5b8c7e07..f584a2d8 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.6.0 + 3.7.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.6.0' +implementation 'com.auth0:java-jwt:3.7.0' ``` ## Available Algorithms From 7fcd1bfece8197f0c44cd020a03045df6cf182de Mon Sep 17 00:00:00 2001 From: Josh Cunningham Date: Mon, 4 Feb 2019 16:02:43 -0800 Subject: [PATCH 096/355] Adding GitHub Issue and PR templates (#323) Adding GitHub Issue and PR templates --- .github/ISSUE_TEMPLATE.md | 38 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 31 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..009ccd24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ +In order to efficiently and accurately address your issue or feature request, please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. Please delete any sections or questions below that do not pertain to this request. + +For general support or usage questions, please use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com/). + +### Description + +Description of the bug or feature request and why it's a problem. Consider including: + +- The use case or overall problem you're trying to solve +- Information about when the problem started + +### Prerequisites + +- [ ] I have checked the documentation for this library in the README. +- [ ] I have checked the [Auth0 Community](https://community.auth0.com/) for related posts. +- [ ] I have checked for related or duplicate [Issues](https://github.com/auth0/java-jwt/issues) and [PRs](https://github.com/auth0/java-jwt/pulls). +- [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). +- [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). +- [ ] I am reporting this to the correct repository (note this library is used by many libraries, such as [auth0-java](https://github.com/auth0/auth0-java)). + +### Environment + +Please provide the following: + +- Version of this library used: +- Version of Java framework used: +- Additional libraries that might be affecting your instance: + +### Reproduction + +Detail the steps taken to reproduce this error and note if this issue can be reproduced consistently or if it is intermittent. + +Please include: + +- Code sample to reproduce the issue +- Log files (redact/remove sensitive information) +- Application settings (redact/remove sensitive information) +- Screenshots, if helpful diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..8675fc7e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +### Changes + +Please describe both what is changing and why this is important. Include: + +- Endpoints added, deleted, deprecated, or changed +- Classes and methods added, deleted, deprecated, or changed +- Screenshots of new or changed UI, if applicable +- A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) +- Any alternative designs or approaches considered + +### References + +Please include relevant links supporting this change such as a: + +- support ticket +- community post +- StackOverflow post +- support forum thread + +### Testing + +Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. + +- [ ] This change adds test coverage +- [ ] This change has been tested on the latest version of Java or why not + +### Checklist + +- [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) +- [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) +- [ ] All existing and new tests complete without errors From 6ffa7032d26ebd69a16b9ed62bd8eef628aee133 Mon Sep 17 00:00:00 2001 From: itdevelopmentapps <31443120+itdevelopmentapps@users.noreply.github.com> Date: Mon, 11 Mar 2019 17:57:53 +0100 Subject: [PATCH 097/355] Support multiple issuers #246 (#288) * Support multiple issuers #246 * Implemented comments after review * Implemented comments after review, rolled back remove claim unit test * Implemented comments after review * Added tests to increase coverage --- .../main/java/com/auth0/jwt/JWTVerifier.java | 17 ++++++--- .../auth0/jwt/interfaces/Verification.java | 2 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 38 ++++++++++++++++++- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6eea86bb..a1c7f82b 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -61,12 +61,12 @@ public static class BaseVerification implements Verification { /** * Require a specific Issuer ("iss") claim. * - * @param issuer the required Issuer value + * @param issuer the required Issuer value. If multiple values are given, the claim must at least match one of them * @return this same Verification instance. */ @Override - public Verification withIssuer(String issuer) { - requireClaim(PublicClaims.ISSUER, issuer); + public Verification withIssuer(String... issuer) { + requireClaim(PublicClaims.ISSUER, issuer == null ? null : Arrays.asList(issuer)); return this; } @@ -90,7 +90,7 @@ public Verification withSubject(String subject) { */ @Override public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, Arrays.asList(audience)); + requireClaim(PublicClaims.AUDIENCE, audience == null ? null : Arrays.asList(audience)); return this; } @@ -398,7 +398,6 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok for (Map.Entry entry : claims.entrySet()) { switch (entry.getKey()) { case PublicClaims.AUDIENCE: - //noinspection unchecked assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); break; case PublicClaims.EXPIRES_AT: @@ -411,7 +410,7 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); break; case PublicClaims.ISSUER: - assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue()); + assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); break; case PublicClaims.JWT_ID: assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); @@ -486,4 +485,10 @@ private void assertValidAudienceClaim(List audience, List value) throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } } + + private void assertValidIssuerClaim(String issuer, List value) { + if (issuer == null || !value.contains(issuer)) { + throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); + } + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index a38468ab..41b1ce3e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -5,7 +5,7 @@ import java.util.Date; public interface Verification { - Verification withIssuer(String issuer); + Verification withIssuer(String... issuer); Verification withSubject(String subject); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d7bcdd2c..e297c59c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -51,10 +51,22 @@ public void shouldValidateIssuer() throws Exception { assertThat(jwt, is(notNullValue())); } + @Test + public void shouldValidateMultipleIssuers() { + String auth0Token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + String otherIssuertoken = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJvdGhlcklzc3VlciJ9.k4BCOJJl-c0_Y-49VD_mtt-u0QABKSV5i3W-RKc74co"; + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("otherIssuer", "auth0") + .build(); + + assertThat(verifier.verify(auth0Token), is(notNullValue())); + assertThat(verifier.verify(otherIssuertoken), is(notNullValue())); + } + @Test public void shouldThrowOnInvalidIssuer() throws Exception { exception.expect(InvalidClaimException.class); - exception.expectMessage("The Claim 'iss' value doesn't match the required one."); + exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; JWTVerifier.init(Algorithm.HMAC256("secret")) .withIssuer("invalid") @@ -62,6 +74,18 @@ public void shouldThrowOnInvalidIssuer() throws Exception { .verify(token); } + @Test + public void shouldThrowOnNullIssuer() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("auth0") + .build() + .verify(token); + } + @Test public void shouldValidateSubject() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; @@ -126,6 +150,18 @@ public void shouldThrowOnInvalidAudience() throws Exception { .verify(token); } + @Test + public void shouldRemoveAudienceWhenPassingNull() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAudience("John") + .withAudience(null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + } + @Test public void shouldThrowOnNullCustomClaimName() throws Exception { exception.expect(IllegalArgumentException.class); From 8946bfe2c6e67622a7254899f1920b7c6d89b7b2 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 14 Mar 2019 15:49:17 -0300 Subject: [PATCH 098/355] Release 3.8.0 (#325) --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e951218b..c83a5cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.8.0](https://github.com/auth0/java-jwt/tree/3.8.0) (2019-03-14) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.7.0...3.8.0) + +**Added** +- Support multiple issuers #246 [\#288](https://github.com/auth0/java-jwt/pull/288) ([itdevelopmentapps](https://github.com/itdevelopmentapps)) + ## [3.7.0](https://github.com/auth0/java-jwt/tree/3.7.0) (2019-01-29) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.6.0...3.7.0) diff --git a/README.md b/README.md index f584a2d8..dd0da007 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.7.0 + 3.8.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.7.0' +implementation 'com.auth0:java-jwt:3.8.0' ``` ## Available Algorithms From aba456be6e63f4133741684b9783dc57f4bebede Mon Sep 17 00:00:00 2001 From: Ervis Zyka Date: Tue, 26 Mar 2019 23:12:32 +0200 Subject: [PATCH 099/355] fix javadoc parameter names (#327) --- lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java index 93e8f176..dc92ff97 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java @@ -32,8 +32,8 @@ boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, * * @param algorithm algorithm name. * @param secretBytes algorithm secret. - * @param header JWT header. - * @param payload JWT payload. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. From 890538970a9699b7251a4bfe4f26e8d7605ad530 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 25 Apr 2019 14:14:17 -0300 Subject: [PATCH 100/355] fix coverage badge (#330) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd0da007..bf61de4d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Java JWT [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) -[![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt/v3.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) +[![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). From e8f5db68a33b45fd1528872d1f35625dbd3e5006 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Fri, 3 May 2019 14:39:15 -0700 Subject: [PATCH 101/355] Setup the CODEOWNERS for pull request reviews (#331) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..6a263cd8 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @auth0/sdk-team-approvers From f1ba5add078f3449ced6972d79f75a9fd39dad24 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 22 May 2019 15:29:17 -0600 Subject: [PATCH 102/355] bump dependencies (#337) --- lib/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 7126e324..6962e326 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,6 +1,6 @@ plugins { id "com.jfrog.bintray" version "1.8.4" - id "com.auth0.gradle.oss-library.java" version "0.8.0" + id "com.auth0.gradle.oss-library.java" version "0.9.0" id "jacoco" } @@ -34,8 +34,8 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' - implementation 'commons-codec:commons-codec:1.11' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' + implementation 'commons-codec:commons-codec:1.12' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' From 39a9f74a536386bdd1b43468552d28cdbe409e14 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 23 May 2019 09:54:32 -0600 Subject: [PATCH 103/355] Release 3.8.1 (#338) --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c83a5cfd..1761e4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.8.1](https://github.com/auth0/java-jwt/tree/3.8.1) (2019-05-22) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.0...3.8.1) + +**Security** +- Bump dependencies and fix security issue [\#337](https://github.com/auth0/java-jwt/pull/337) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.8.0](https://github.com/auth0/java-jwt/tree/3.8.0) (2019-03-14) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.7.0...3.8.0) diff --git a/README.md b/README.md index bf61de4d..5856424b 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.8.0 + 3.8.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.8.0' +implementation 'com.auth0:java-jwt:3.8.1' ``` ## Available Algorithms From 4dca9515c7c742b727dc71968174210241eea88d Mon Sep 17 00:00:00 2001 From: Daniel Brodsky Date: Mon, 15 Jul 2019 16:00:52 -0700 Subject: [PATCH 104/355] Fix: updated jackson-databind to 2.9.9.1 to block CVE-2019-12814 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 6962e326..4d2064f1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9.1' implementation 'commons-codec:commons-codec:1.12' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From 0b2c8a716ca110c13ba07a0d784d446ad7ba36a6 Mon Sep 17 00:00:00 2001 From: Daniel Brodsky Date: Thu, 8 Aug 2019 19:48:17 -0700 Subject: [PATCH 105/355] Fix: updated jackson-databind to 2.9.9.3 blocks CVE-2019-12814, CVE-2019-14379 and CVE-2019-14439 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 4d2064f1..d3659a3a 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9.3' implementation 'commons-codec:commons-codec:1.12' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From e4811e5baf0af36de902cc2df95e084a444408a8 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 15 Aug 2019 13:13:48 -0500 Subject: [PATCH 106/355] Release 3.8.2 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1761e4c9..d2333dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.8.2](https://github.com/auth0/java-jwt/tree/3.8.2) (2019-08-15) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.1...3.8.2) + +**Security** +- Fix: updated jackson-databind to 2.9.9.3 to block CVE [\#347](https://github.com/auth0/java-jwt/pull/347) ([danbrodsky](https://github.com/danbrodsky)) + ## [3.8.1](https://github.com/auth0/java-jwt/tree/3.8.1) (2019-05-22) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.0...3.8.1) diff --git a/README.md b/README.md index 5856424b..56e903ad 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.8.1 + 3.8.2 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.8.1' +implementation 'com.auth0:java-jwt:3.8.2' ``` ## Available Algorithms From 7adb0cfaf6516d41b36f749ac869f788a1b27f7b Mon Sep 17 00:00:00 2001 From: Daniel Brodsky Date: Fri, 20 Sep 2019 11:44:36 -0700 Subject: [PATCH 107/355] Fix: updated jackson-databind to 2.10.0.pr3 to block CVE-2019-14540 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index d3659a3a..4cb57f61 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9.3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0.pr3' implementation 'commons-codec:commons-codec:1.12' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From 68f9cf5bff96536967978bfdaa6743d5ce098e5e Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 25 Sep 2019 10:09:03 -0500 Subject: [PATCH 108/355] Release 3.8.3 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2333dfe..61695acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.8.3](https://github.com/auth0/java-jwt/tree/3.8.3) (2019-09-25) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.2...3.8.3) + +**Security** +- Fix: updated jackson-databind to 2.10.0.pr3 to block CVE [\#356](https://github.com/auth0/java-jwt/pull/356) ([danbrodsky](https://github.com/danbrodsky)) + ## [3.8.2](https://github.com/auth0/java-jwt/tree/3.8.2) (2019-08-15) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.1...3.8.2) diff --git a/README.md b/README.md index 56e903ad..b3affec5 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.8.2 + 3.8.3 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.8.2' +implementation 'com.auth0:java-jwt:3.8.3' ``` ## Available Algorithms From 67d5d228e2a19c7b1805da45e9a46cb5fb050d40 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Tue, 8 Oct 2019 10:18:19 -0700 Subject: [PATCH 109/355] Setup the CODEOWNERS for pull request reviews --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6a263cd8..073d5ffc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/sdk-team-approvers +* @auth0/dx-sdk-approver From 12450227ed5d517f56c0d33643065fbdba767308 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Tue, 8 Oct 2019 12:08:33 -0700 Subject: [PATCH 110/355] Setup the CODEOWNERS for pull request reviews --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 073d5ffc..c9ff4921 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdk-approver +* @auth0/dx-sdks-approver From 9d6968292f886f4054ddd313c50754b9e623269e Mon Sep 17 00:00:00 2001 From: Josh Cunningham Date: Fri, 25 Oct 2019 17:03:23 -0700 Subject: [PATCH 111/355] Setup the .github/stale.yml for Probot:Stale --- .github/stale.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..b2e13fc7 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,20 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 90 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +daysUntilClose: 7 + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: [] + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: true + +# Label to use when marking as stale +staleLabel: closed:stale + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ \ No newline at end of file From c42476df837b54c20bb5291ba4e265d057cf7914 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 5 Nov 2019 15:12:37 +0000 Subject: [PATCH 112/355] fixing pr changes request not real code change just test fixing to remove confusions in the test readability --- .../main/java/com/auth0/jwt/JWTCreator.java | 5 ++-- .../java/com/auth0/jwt/JWTCreatorTest.java | 29 +++++++++++++++---- .../test/java/com/auth0/jwt/JsonMatcher.java | 4 +++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 4ab9906f..f7f15602 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -67,14 +67,15 @@ public static class Builder { /** * Add specific Claims to set as the Header. * If provided map is null then nothing is changed - * If provided map contains a header with null value then that header will be removed from the header claims + * If provided map contains a claim with null value then that claim will be removed from the header * * @param headerClaims the values to use as Claims in the token's Header. * @return this same Builder instance. */ public Builder withHeader(Map headerClaims) { - if (headerClaims == null) + if (headerClaims == null) { return this; + } for (Map.Entry entry : headerClaims.entrySet()) { if (entry.getValue() == null) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index f2a54803..47ba0f05 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -1,6 +1,7 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import org.apache.commons.codec.binary.Base64; @@ -65,34 +66,50 @@ public void shouldReturnBuilderIfNullMapIsProvided() throws Exception { @Test public void shouldOverwriteExistingIfHeadersMapContainsTheSameKey() throws Exception { Map header = new HashMap(); - header.put("test", 456); + header.put(PublicClaims.KEY_ID, "xyz"); String signed = JWTCreator.init() - .withClaim("test", 123) + .withKeyId("abc") .withHeader(header) .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry("test", 456)); + assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + } + + @Test + public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() throws Exception { + Map header = new HashMap(); + header.put(PublicClaims.KEY_ID, "xyz"); + + String signed = JWTCreator.init() + .withHeader(header) + .withKeyId("abc") + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "abc")); } @Test public void shouldRemoveHeaderIfTheValueIsNull() throws Exception { Map header = new HashMap(); - header.put("test", null); + header.put(PublicClaims.KEY_ID, null); header.put("test2", "isSet"); String signed = JWTCreator.init() - .withClaim("test", 123) + .withKeyId("test") .withHeader(header) .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry("test", null)); + assertThat(headerJson, JsonMatcher.isNotPresent(PublicClaims.KEY_ID)); assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); } diff --git a/lib/src/test/java/com/auth0/jwt/JsonMatcher.java b/lib/src/test/java/com/auth0/jwt/JsonMatcher.java index f03547d4..b09ab187 100644 --- a/lib/src/test/java/com/auth0/jwt/JsonMatcher.java +++ b/lib/src/test/java/com/auth0/jwt/JsonMatcher.java @@ -68,6 +68,10 @@ public static JsonMatcher hasEntry(String key, Matcher valueMatcher) { return new JsonMatcher(key, null, valueMatcher); } + public static JsonMatcher isNotPresent(String key) { + return new JsonMatcher(key, null, null); + } + private String getStringKey(String key) { return "\"" + key + "\":"; } From bb8371797dbe1d1119890110f22c4aeb8dd60d58 Mon Sep 17 00:00:00 2001 From: Maxim Balan Date: Tue, 5 Nov 2019 16:35:20 +0000 Subject: [PATCH 113/355] Update lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java Co-Authored-By: Luciano Balmaceda --- lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 47ba0f05..05dd1e30 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -64,7 +64,7 @@ public void shouldReturnBuilderIfNullMapIsProvided() throws Exception { } @Test - public void shouldOverwriteExistingIfHeadersMapContainsTheSameKey() throws Exception { + public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() throws Exception { Map header = new HashMap(); header.put(PublicClaims.KEY_ID, "xyz"); @@ -417,4 +417,4 @@ public void shouldAcceptCustomArrayClaimOfTypeLong() throws Exception { String[] parts = jwt.split("\\."); assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); } -} \ No newline at end of file +} From d89d746c1a10c128d257a3ef6187bd72937cc0c8 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 20 Dec 2019 17:05:29 -0600 Subject: [PATCH 114/355] Support serialization of DecodedJWT --- .../main/java/com/auth0/jwt/JWTDecoder.java | 5 ++- .../java/com/auth0/jwt/impl/BasicHeader.java | 5 ++- .../java/com/auth0/jwt/impl/PayloadImpl.java | 6 ++- .../java/com/auth0/jwt/JWTDecoderTest.java | 42 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 34503f43..b14c2ac3 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -9,6 +9,7 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.StringUtils; +import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map; @@ -17,7 +18,9 @@ * The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation. */ @SuppressWarnings("WeakerAccess") -final class JWTDecoder implements DecodedJWT { +final class JWTDecoder implements DecodedJWT, Serializable { + + private static final long serialVersionUID = 1873362438023312895L; private final String[] parts; private final Header header; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index b1d81a11..0a782268 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectReader; +import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -14,7 +15,9 @@ /** * The BasicHeader class implements the Header interface. */ -class BasicHeader implements Header { +class BasicHeader implements Header, Serializable { + private static final long serialVersionUID = -4659137688548605095L; + private final String algorithm; private final String type; private final String contentType; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index d3b1dc0c..2c5558f2 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectReader; +import java.io.Serializable; import java.util.*; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -12,7 +13,10 @@ /** * The PayloadImpl class implements the Payload interface. */ -class PayloadImpl implements Payload { +class PayloadImpl implements Payload, Serializable { + + private static final long serialVersionUID = 1659021498824562311L; + private final String issuer; private final String subject; private final List audience; diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index e164dc07..dfe59182 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; @@ -289,6 +290,34 @@ public void shouldGetAvailableClaims() throws Exception { assertThat(jwt.getClaims().get("extraClaim"), is(notNullValue())); } + @Test + public void shouldSerializeAndDeserialize() throws Exception { + DecodedJWT originalJwt = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MCwibmJmIjoxMjM0NTY3ODkwLCJqdGkiOiJodHRwczovL2p3dC5pby8iLCJhdWQiOiJodHRwczovL2RvbWFpbi5hdXRoMC5jb20iLCJzdWIiOiJsb2dpbiIsImlzcyI6ImF1dGgwIiwiZXh0cmFDbGFpbSI6IkpvaG4gRG9lIn0.2_0nxDPJwOk64U5V5V9pt8U92jTPJbGsHYQ35HYhbdE"); + + assertThat(originalJwt, is(instanceOf(Serializable.class))); + + byte[] serialized = serialize(originalJwt); + DecodedJWT deserializedJwt = (DecodedJWT) deserialize(serialized); + + assertThat(originalJwt.getHeader(), is(equalTo(deserializedJwt.getHeader()))); + assertThat(originalJwt.getPayload(), is(equalTo(deserializedJwt.getPayload()))); + assertThat(originalJwt.getSignature(), is(equalTo(deserializedJwt.getSignature()))); + assertThat(originalJwt.getToken(), is(equalTo(deserializedJwt.getToken()))); + assertThat(originalJwt.getAlgorithm(), is(equalTo(deserializedJwt.getAlgorithm()))); + assertThat(originalJwt.getAudience(), is(equalTo(deserializedJwt.getAudience()))); + assertThat(originalJwt.getContentType(), is(equalTo(deserializedJwt.getContentType()))); + assertThat(originalJwt.getExpiresAt(), is(equalTo(deserializedJwt.getExpiresAt()))); + assertThat(originalJwt.getId(), is(equalTo(deserializedJwt.getId()))); + assertThat(originalJwt.getIssuedAt(), is(equalTo(deserializedJwt.getIssuedAt()))); + assertThat(originalJwt.getIssuer(), is(equalTo(deserializedJwt.getIssuer()))); + assertThat(originalJwt.getKeyId(), is(equalTo(deserializedJwt.getKeyId()))); + assertThat(originalJwt.getNotBefore(), is(equalTo(deserializedJwt.getNotBefore()))); + assertThat(originalJwt.getSubject(), is(equalTo(deserializedJwt.getSubject()))); + assertThat(originalJwt.getType(), is(equalTo(deserializedJwt.getType()))); + assertThat(originalJwt.getClaims().get("extraClaim").asString(), + is(equalTo(deserializedJwt.getClaims().get("extraClaim").asString()))); + } + //Helper Methods private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) { @@ -297,4 +326,17 @@ private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signa return JWT.decode(String.format("%s.%s.%s", header, body, signature)); } + private static byte[] serialize(Object obj) throws IOException { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + ObjectOutputStream o = new ObjectOutputStream(b); + o.writeObject(obj); + return b.toByteArray(); + } + + private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { + ByteArrayInputStream b = new ByteArrayInputStream(bytes); + ObjectInputStream o = new ObjectInputStream(b); + return o.readObject(); + } + } \ No newline at end of file From 18e8b8bc84ea8f541a58104ad48086a827f6e258 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 2 Jan 2020 12:08:35 -0600 Subject: [PATCH 115/355] Release 3.9.0 --- CHANGELOG.md | 9 +++++++++ README.md | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61695acf..9c819ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [3.9.0](https://github.com/auth0/java-jwt/tree/3.9.0) (2020-01-02) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.3...3.9.0) + +**Added** +- Support serialization of DecodedJWT [\#370](https://github.com/auth0/java-jwt/pull/370) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Fixing JwtCreator builder when setting headers as a map [\#320](https://github.com/auth0/java-jwt/pull/320) ([maxbalan](https://github.com/maxbalan)) + ## [3.8.3](https://github.com/auth0/java-jwt/tree/3.8.3) (2019-09-25) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.2...3.8.3) diff --git a/README.md b/README.md index b3affec5..bbee8607 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o com.auth0 java-jwt - 3.8.3 + 3.9.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.8.3' +implementation 'com.auth0:java-jwt:3.9.0' ``` ## Available Algorithms From d9cebe1b4652ef53e47a1b3602ca0db2232b18c2 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 23 Jan 2020 14:32:19 -0600 Subject: [PATCH 116/355] Move Javadoc to Verification interface --- .../main/java/com/auth0/jwt/JWTVerifier.java | 132 +----------------- .../auth0/jwt/interfaces/Verification.java | 131 +++++++++++++++++ 2 files changed, 132 insertions(+), 131 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index a1c7f82b..ff25db09 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -39,9 +39,6 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { return new BaseVerification(algorithm); } - /** - * The Verification class holds the Claims required by a JWT to be valid. - */ public static class BaseVerification implements Verification { private final Algorithm algorithm; private final Map claims; @@ -58,50 +55,24 @@ public static class BaseVerification implements Verification { this.defaultLeeway = 0; } - /** - * Require a specific Issuer ("iss") claim. - * - * @param issuer the required Issuer value. If multiple values are given, the claim must at least match one of them - * @return this same Verification instance. - */ @Override public Verification withIssuer(String... issuer) { requireClaim(PublicClaims.ISSUER, issuer == null ? null : Arrays.asList(issuer)); return this; } - /** - * Require a specific Subject ("sub") claim. - * - * @param subject the required Subject value - * @return this same Verification instance. - */ @Override public Verification withSubject(String subject) { requireClaim(PublicClaims.SUBJECT, subject); return this; } - /** - * Require a specific Audience ("aud") claim. - * - * @param audience the required Audience value - * @return this same Verification instance. - */ @Override public Verification withAudience(String... audience) { requireClaim(PublicClaims.AUDIENCE, audience == null ? null : Arrays.asList(audience)); return this; } - /** - * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * Setting a specific leeway value on a given Claim will override this value for that Claim. - * - * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ @Override public Verification acceptLeeway(long leeway) throws IllegalArgumentException { assertPositive(leeway); @@ -109,14 +80,6 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { return this; } - /** - * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. - * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Expires At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); @@ -124,14 +87,6 @@ public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException return this; } - /** - * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. - * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Not Before Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); @@ -139,14 +94,6 @@ public Verification acceptNotBefore(long leeway) throws IllegalArgumentException return this; } - /** - * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. - * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway - * - * @param leeway the window in seconds in which the Issued At Claim will still be valid. - * @return this same Verification instance. - * @throws IllegalArgumentException if leeway is negative. - */ @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); @@ -154,34 +101,18 @@ public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException return this; } - /** - * Skip the Issued At ("iat") date verification. By default, the verification is performed. - */ + @Override public Verification ignoreIssuedAt() { this.ignoreIssuedAt = true; return this; } - /** - * Require a specific JWT Id ("jti") claim. - * - * @param jwtId the required Id value - * @return this same Verification instance. - */ @Override public Verification withJWTId(String jwtId) { requireClaim(PublicClaims.JWT_ID, jwtId); return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -189,14 +120,6 @@ public Verification withClaim(String name, Boolean value) throws IllegalArgument return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); @@ -204,14 +127,6 @@ public Verification withClaim(String name, Integer value) throws IllegalArgument return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, Long value) throws IllegalArgumentException { assertNonNull(name); @@ -219,14 +134,6 @@ public Verification withClaim(String name, Long value) throws IllegalArgumentExc return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); @@ -234,14 +141,6 @@ public Verification withClaim(String name, Double value) throws IllegalArgumentE return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); @@ -249,14 +148,6 @@ public Verification withClaim(String name, String value) throws IllegalArgumentE return this; } - /** - * Require a specific Claim value. - * - * @param name the Claim's name. - * @param value the Claim's value. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withClaim(String name, Date value) throws IllegalArgumentException { assertNonNull(name); @@ -264,14 +155,6 @@ public Verification withClaim(String name, Date value) throws IllegalArgumentExc return this; } - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); @@ -279,14 +162,6 @@ public Verification withArrayClaim(String name, String... items) throws IllegalA return this; } - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); @@ -294,11 +169,6 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal return this; } - /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. - * - * @return a new JWTVerifier instance. - */ @Override public JWTVerifier build() { return this.build(new ClockImpl()); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 41b1ce3e..511e59b7 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -4,40 +4,171 @@ import java.util.Date; +/** + * Holds the Claims and claim-based configurations required for a JWT to be considered valid. + */ public interface Verification { + /** + * Require a specific Issuer ("iss") claim. + * + * @param issuer the required Issuer value. If multiple values are given, the claim must at least match one of them + * @return this same Verification instance. + */ Verification withIssuer(String... issuer); + /** + * Require a specific Subject ("sub") claim. + * + * @param subject the required Subject value + * @return this same Verification instance. + */ Verification withSubject(String subject); + /** + * Require a specific Audience ("aud") claim. + * + * @param audience the required Audience value + * @return this same Verification instance. + */ Verification withAudience(String... audience); + /** + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * Setting a specific leeway value on a given Claim will override this value for that Claim. + * + * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ Verification acceptLeeway(long leeway) throws IllegalArgumentException; + /** + * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. + * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Expires At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ Verification acceptExpiresAt(long leeway) throws IllegalArgumentException; + /** + * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. + * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Not Before Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ Verification acceptNotBefore(long leeway) throws IllegalArgumentException; + /** + * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. + * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * + * @param leeway the window in seconds in which the Issued At Claim will still be valid. + * @return this same Verification instance. + * @throws IllegalArgumentException if leeway is negative. + */ Verification acceptIssuedAt(long leeway) throws IllegalArgumentException; + /** + * Require a specific JWT Id ("jti") claim. + * + * @param jwtId the required Id value + * @return this same Verification instance. + */ Verification withJWTId(String jwtId); + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, Boolean value) throws IllegalArgumentException; + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, Integer value) throws IllegalArgumentException; + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, Long value) throws IllegalArgumentException; + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, Double value) throws IllegalArgumentException; + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, String value) throws IllegalArgumentException; + /** + * Require a specific Claim value. + * + * @param name the Claim's name. + * @param value the Claim's value. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withClaim(String name, Date value) throws IllegalArgumentException; + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withArrayClaim(String name, String... items) throws IllegalArgumentException; + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; + /** + * Skip the Issued At ("iat") date verification. By default, the verification is performed. + */ Verification ignoreIssuedAt(); + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * + * @return a new JWTVerifier instance. + */ JWTVerifier build(); } From a3e8a6b766253dcd548c79cff2c06594519abcb5 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 23 Jan 2020 16:19:01 -0600 Subject: [PATCH 117/355] Update CHANGELOG for breaking changes in 3.4.0 --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c819ea0..e03dd703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,8 +69,15 @@ ## [3.4.0](https://github.com/auth0/java-jwt/tree/3.4.0) (2018-06-13) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.3.0...3.4.0) +**Breaking Changes** +- Fix for [\#236](https://github.com/auth0/java-jwt/pull/236) - refactored HMACAlgorithm so that it doesn't throw an UnsupportedEncodingException [\#242](https://github.com/auth0/java-jwt/pull/242) ([obecker](https://github.com/obecker)). + +Clients using the following methods may need to update their code to not catch an `UnsupportedEncodingException`: +- `public static Algorithm HMAC384(String secret)` +- `public static Algorithm HMAC256(String secret)` +- `public static Algorithm HMAC512(String secret)` + **Changed** -- Fix for issue #236 - refactored HMACAlgorithm so that it doesn't throw an UnsupportedEncodingException [\#242](https://github.com/auth0/java-jwt/pull/242) ([obecker](https://github.com/obecker)) - Throw JWTDecodeException when date claim format is invalid [\#241](https://github.com/auth0/java-jwt/pull/241) ([lbalmaceda](https://github.com/lbalmaceda)) **Security** From e88b98b78b6a9082cd31f95c950fa4e42bf53fdb Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 24 Jan 2020 13:21:16 -0600 Subject: [PATCH 118/355] Link to gist demonstrating how to read keys --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bbee8607..104793cf 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ RSAPrivateKey privateKey = //Get the key instance Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); ``` +> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, you can see the example [here](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). + #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: @@ -138,7 +140,6 @@ try { If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. - ### Verify a Token You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. From 405cc605136addfce4eaa635844a3d6e71d90679 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 24 Jan 2020 13:27:37 -0600 Subject: [PATCH 119/355] whitespace fix --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 104793cf..bc1c44a7 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ try { If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. + ### Verify a Token You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. From 3cca8c009e72a9a2dcf2a6d21521980dc73faf0d Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 24 Jan 2020 14:03:58 -0600 Subject: [PATCH 120/355] Update wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc1c44a7..d071b958 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ RSAPrivateKey privateKey = //Get the key instance Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); ``` -> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, you can see the example [here](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). +> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). #### Using a KeyProvider: From 1b5ef2914a4de3e21819f475488f7d76af1bcd81 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 3 Feb 2020 13:29:56 -0300 Subject: [PATCH 121/355] allow to customize the typ header claim --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 4 +++- .../test/java/com/auth0/jwt/JWTCreatorTest.java | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f7f15602..deda9813 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -316,7 +316,9 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea throw new IllegalArgumentException("The Algorithm cannot be null."); } headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); - headerClaims.put(PublicClaims.TYPE, "JWT"); + if (!headerClaims.containsKey(PublicClaims.TYPE)) { + headerClaims.put(PublicClaims.TYPE, "JWT"); + } String signingKeyId = algorithm.getSigningKeyId(); if (signingKeyId != null) { withKeyId(signingKeyId); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 05dd1e30..471ff38b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -12,6 +12,7 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateKey; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -292,7 +293,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception { } @Test - public void shouldSetCorrectTypeInTheHeader() throws Exception { + public void shouldSetDefaultTypeInTheHeader() throws Exception { String signed = JWTCreator.init() .sign(Algorithm.HMAC256("secret")); @@ -302,6 +303,19 @@ public void shouldSetCorrectTypeInTheHeader() throws Exception { assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); } + @Test + public void shouldSetCustomTypeInTheHeader() throws Exception { + Map header = Collections.singletonMap("typ", "passport"); + String signed = JWTCreator.init() + .withHeader(header) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "passport")); + } + @Test public void shouldSetEmptySignatureIfAlgorithmIsNone() throws Exception { String signed = JWTCreator.init() From 9742669962c3bc1c0153a39b19e574fad94a704a Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 3 Feb 2020 13:55:01 -0300 Subject: [PATCH 122/355] add javadoc url and badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d071b958..fcb27e2d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) +[![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -12,6 +13,8 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o ## Installation +The library is available on both Maven Central and Bintray, and the Javadoc published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). + ### Maven ```xml From 99195bf74825166bb85893d6687a6ef5362803ad Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 3 Feb 2020 14:48:15 -0300 Subject: [PATCH 123/355] Update README.md Co-Authored-By: Rita Zerrizuela --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcb27e2d..b63599a2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o ## Installation -The library is available on both Maven Central and Bintray, and the Javadoc published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). +The library is available on both Maven Central and Bintray, and the Javadoc is published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). ### Maven From 0a8e50e6be63265bc698005b3e33f05b137e81ac Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 12 Feb 2020 15:12:56 -0600 Subject: [PATCH 124/355] Update tests to use valid Base64 URL-encoded tokens --- .../jwt/algorithms/ECDSAAlgorithmTest.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) 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 8968ed9f..b50832d1 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -21,7 +21,6 @@ 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 com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; @@ -75,7 +74,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); Algorithm algorithm = Algorithm.ECDSA256(key); algorithm.verify(JWT.decode(jwt)); @@ -95,7 +94,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignatureWithBothKeys() thro exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -200,7 +199,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignature() throws Exception 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=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UtccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); Algorithm algorithm = Algorithm.ECDSA384(key); algorithm.verify(JWT.decode(jwt)); @@ -220,7 +219,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignatureWithBothKeys() thro 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=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -325,7 +324,7 @@ public void shouldThrowOnECDSA512VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); Algorithm algorithm = Algorithm.ECDSA512(key); algorithm.verify(JWT.decode(jwt)); @@ -345,7 +344,7 @@ public void shouldThrowECDSA512VerificationWithDERSignatureWithBothKeys() throws exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -519,7 +518,7 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception private static final byte[] ES512HeaderBytes = ES512Header.getBytes(StandardCharsets.UTF_8); private static final byte[] auth0IssPayloadBytes = auth0IssPayload.getBytes(StandardCharsets.UTF_8); - + @Test public void shouldDoECDSA256Signing() throws Exception { Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); @@ -549,7 +548,7 @@ public void shouldDoECDSA256SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); Algorithm algorithm = Algorithm.ECDSA256(provider); - + String jwt = asJWT(algorithm, ES256Header, auth0IssPayload); assertSignaturePresent(jwt); @@ -607,7 +606,7 @@ public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); Algorithm algorithm = Algorithm.ECDSA384(provider); - + String jwt = asJWT(algorithm, ES384Header, auth0IssPayload); assertSignaturePresent(jwt); @@ -642,7 +641,7 @@ public void shouldFailOnECDSA384SigningWhenUsingPublicKey() throws Exception { 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 jwt = asJWT(algorithmSign, ES512Header, auth0IssPayload); assertSignaturePresent(jwt); @@ -652,7 +651,7 @@ public void shouldDoECDSA512Signing() throws Exception { @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 jwt = asJWT(algorithm, ES512Header, auth0IssPayload); assertSignaturePresent(jwt); @@ -668,7 +667,7 @@ public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); Algorithm algorithm = Algorithm.ECDSA512(provider); - + String jwt = asJWT(algorithm, ES512Header, auth0IssPayload); assertSignaturePresent(jwt); @@ -843,7 +842,7 @@ public void shouldSignAndVerifyWithECDSA256() throws Exception { 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 header384 = "eyJhbGciOiJFUzM4NCJ9"; - String body = "eyJpc3MiOiJhdXRoMCJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { String jwt = asJWT(algorithm384, header384, body); @@ -855,7 +854,7 @@ public void shouldSignAndVerifyWithECDSA384() throws Exception { 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 header512 = "eyJhbGciOiJFUzUxMiJ9"; - String body = "eyJpc3MiOiJhdXRoMCJ9"; + String body = "eyJpc3MiOiJhdXRoMCJ9"; for (int i = 0; i < 10; i++) { String jwt = asJWT(algorithm512, header512, body); @@ -1171,7 +1170,7 @@ public void shouldBeEqualSignatureMethodDecodeResults() throws Exception { /** * Test deprecated signing method error handling. - * + * * @see {@linkplain #shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull} * @throws Exception expected exception */ From 0908db724a240974874c06b40f7025c3950d50aa Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 12 Feb 2020 15:39:00 -0600 Subject: [PATCH 125/355] Update to Gradle 6.1.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 18 +++++++++++++++++- gradlew.bat | 18 +++++++++++++++++- lib/build.gradle | 2 +- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 29953ea141f55e3b8fc691d31b5ca8816d89fa87..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch delta 47360 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 delta 47987 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 P{tQ5B1&<&s7}); Date: Mon, 20 Aug 2018 17:01:58 +0200 Subject: [PATCH 126/355] Support map / list claims --- .../main/java/com/auth0/jwt/JWTCreator.java | 90 +++++++++++++++++++ .../java/com/auth0/jwt/JWTCreatorTest.java | 29 +++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index deda9813..c33fcbe3 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -15,7 +15,9 @@ import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; /** * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) from a given Header and Payload content. @@ -303,6 +305,94 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE return this; } + /** + * Add a custom Map Claim with the given items.

+ * + * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types + * Boolean, Integer, Long, Double, String and Date. + * + * @param name the Claim's name. + * @param items the Claim's key-values. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withClaim(String name, Map map) throws IllegalArgumentException { + assertNonNull(name); + if(!validateClaim(map)) { + throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); + } + addClaim(name, map); + return this; + } + + /** + * Add a custom List Claim with the given items. + * + * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types + * Boolean, Integer, Long, Double, String and Date. + * + * @param name the Claim's name. + * @param items the Claim's list of values. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + + public Builder withClaim(String name, List map) throws IllegalArgumentException { + assertNonNull(name); + // validate map contents + if(!validateClaim(map)) { + throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); + } + addClaim(name, map); + return this; + } + + private static boolean validateClaim(Map map) { + for (Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if(value != null && !isSupported(value)) { + return false; + } + + if(entry.getKey() == null || !(entry.getKey() instanceof String)) { + return false; + } + } + return true; + } + + private static boolean validateClaim(List list) { + for (Object object : list) { + if(object != null && !isSupported(object)) { + return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private static boolean isSupported(Object value) { + if(value != null) { + if(value instanceof List) { + return validateClaim((List)value); + } else if(value instanceof Map) { + return validateClaim((Map)value); + } else { + return isBasicType(value); + } + } + return true; + } + + private static boolean isBasicType(Object value) { + Class c = value.getClass(); + + if(c.isArray()) { + return c == Integer[].class || c == Long[].class || c == String[].class; + } + return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Boolean.class; + } + /** * Creates a new JWT and signs is with the given algorithm * diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 471ff38b..629232f7 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -431,4 +431,31 @@ public void shouldAcceptCustomArrayClaimOfTypeLong() throws Exception { String[] parts = jwt.split("\\."); assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); } -} + + @Test + public void shouldAcceptCustomClaimOfTypeObject() throws Exception { + Map data = new HashMap<>(); + data.put("test1", "abc"); + data.put("test2", "def"); + String jwt = JWTCreator.init() + .withClaim("data", data) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + assertThat(parts[1], is("eyJkYXRhIjp7InRlc3QyIjoiZGVmIiwidGVzdDEiOiJhYmMifX0")); + } + + @Test + public void shouldRefuseCustomClaimOfTypeUserPojo() throws Exception{ + Map data = new HashMap<>(); + data.put("test1", new UserPojo("Michael", 255)); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); + } + +} \ No newline at end of file From 1470ba1cbec2d233ef56fa27e72d10420d54c536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Thu, 9 May 2019 12:19:56 +0200 Subject: [PATCH 127/355] Improve code coverage for JWTCreator --- .../main/java/com/auth0/jwt/JWTCreator.java | 19 +- .../java/com/auth0/jwt/JWTCreatorTest.java | 183 ++++++++++++++++++ 2 files changed, 192 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index c33fcbe3..624406b1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -348,9 +348,10 @@ public Builder withClaim(String name, List map) throws IllegalArgumentEx } private static boolean validateClaim(Map map) { + // do not accept null values in maps for (Entry entry : map.entrySet()) { Object value = entry.getValue(); - if(value != null && !isSupported(value)) { + if(value == null || !isSupported(value)) { return false; } @@ -362,6 +363,7 @@ private static boolean validateClaim(Map map) { } private static boolean validateClaim(List list) { + // accept null values in list for (Object object : list) { if(object != null && !isSupported(object)) { return false; @@ -372,16 +374,13 @@ private static boolean validateClaim(List list) { @SuppressWarnings("unchecked") private static boolean isSupported(Object value) { - if(value != null) { - if(value instanceof List) { - return validateClaim((List)value); - } else if(value instanceof Map) { - return validateClaim((Map)value); - } else { - return isBasicType(value); - } + if(value instanceof List) { + return validateClaim((List)value); + } else if(value instanceof Map) { + return validateClaim((Map)value); + } else { + return isBasicType(value); } - return true; } private static boolean isBasicType(Object value) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 629232f7..7871973f 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -4,6 +4,8 @@ import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -12,9 +14,12 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.is; @@ -458,4 +463,182 @@ public void shouldRefuseCustomClaimOfTypeUserPojo() throws Exception{ .sign(Algorithm.HMAC256("secret")); } + + @SuppressWarnings("unchecked") + @Test + public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { + Map data = new HashMap<>(); + + // simple types + data.put("string", "abc"); + data.put("integer", 1); + data.put("long", Long.MAX_VALUE); + data.put("double", 123.456d); + data.put("date", new Date(123L)); + data.put("boolean", true); + + // array types + data.put("intArray", new Integer[]{3, 5}); + data.put("longArray", new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}); + data.put("stringArray", new String[]{"string"}); + + data.put("list", Arrays.asList("a", "b", "c")); + + Map sub = new HashMap<>(); + sub.put("subKey", "subValue"); + + data.put("map", sub); + + String jwt = JWTCreator.init() + .withClaim("data", data) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + Map map = (Map) mapper.readValue(body, Map.class).get("data"); + + assertThat(map.get("string"), is("abc")); + assertThat(map.get("integer"), is(1)); + assertThat(map.get("long"), is(Long.MAX_VALUE)); + assertThat(map.get("double"), is(123.456d)); + assertThat(map.get("date"), is(123)); + assertThat(map.get("boolean"), is(true)); + + // array types + assertThat(map.get("intArray"), is(Arrays.asList(new Integer[]{3, 5}))); + assertThat(map.get("longArray"), is(Arrays.asList(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}))); + assertThat(map.get("stringArray"), is(Arrays.asList(new String[]{"string"}))); + + // list + assertThat(map.get("list"), is(Arrays.asList("a", "b", "c"))); + assertThat(map.get("map"), is(sub)); + + } + + @SuppressWarnings("unchecked") + @Test + public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { + List data = new ArrayList<>(); + + // simple types + data.add("abc"); + data.add(1); + data.add(Long.MAX_VALUE); + data.add(123.456d); + data.add(new Date(123L)); + data.add(true); + + // array types + data.add(new Integer[]{3, 5}); + data.add(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}); + data.add(new String[]{"string"}); + + data.add(Arrays.asList("a", "b", "c")); + + Map sub = new HashMap<>(); + sub.put("subKey", "subValue"); + + data.add(sub); + + String jwt = JWTCreator.init() + .withClaim("data", data) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + List list = (List) mapper.readValue(body, Map.class).get("data"); + + assertThat(list.get(0), is("abc")); + assertThat(list.get(1), is(1)); + assertThat(list.get(2), is(Long.MAX_VALUE)); + assertThat(list.get(3), is(123.456d)); + assertThat(list.get(4), is(123)); + assertThat(list.get(5), is(true)); + + // array types + assertThat(list.get(6), is(Arrays.asList(new Integer[]{3, 5}))); + assertThat(list.get(7), is(Arrays.asList(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}))); + assertThat(list.get(8), is(Arrays.asList(new String[]{"string"}))); + + // list + assertThat(list.get(9), is(Arrays.asList("a", "b", "c"))); + assertThat(list.get(10), is(sub)); + + } + + @Test + public void shouldAcceptCustomClaimForNullListItem() throws Exception{ + Map data = new HashMap<>(); + data.put("test1", Arrays.asList("a", null, "c")); + + JWTCreator.init() + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomClaimForNullMapValue() throws Exception{ + Map data = new HashMap<>(); + data.put("subKey", null); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomClaimForNullMapKey() throws Exception{ + Map data = new HashMap<>(); + data.put(null, "subValue"); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomMapClaimForNonStringKey() throws Exception{ + Map data = new HashMap<>(); + data.put(new Object(), "value"); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("pojo", (Map)data) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomListClaimForUnknownListElement() throws Exception{ + List list = Arrays.asList(new UserPojo("Michael", 255)); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception{ + List list = new ArrayList<>(); + list.add(new Object[] {"test"}); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); + } + } \ No newline at end of file From 0e5e12ff33a9f83f45c0f21e646f1a6bf6e08a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Tue, 11 Feb 2020 21:42:47 +0100 Subject: [PATCH 128/355] Initial QA changes --- .../main/java/com/auth0/jwt/JWTCreator.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 624406b1..f4be98f7 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -297,7 +297,7 @@ public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgume * @param name the Claim's name. * @param items the Claim's value. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type */ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { assertNonNull(name); @@ -306,18 +306,18 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE } /** - * Add a custom Map Claim with the given items.

- * + * Add a custom Map Claim with the given items. * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types * Boolean, Integer, Long, Double, String and Date. * * @param name the Claim's name. - * @param items the Claim's key-values. + * @param map the Claim's key-values. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type */ public Builder withClaim(String name, Map map) throws IllegalArgumentException { assertNonNull(name); + // validate map contents if(!validateClaim(map)) { throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); } @@ -332,18 +332,18 @@ public Builder withClaim(String name, Map map) throws IllegalArg * Boolean, Integer, Long, Double, String and Date. * * @param name the Claim's name. - * @param items the Claim's list of values. + * @param list the Claim's list of values. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type */ - public Builder withClaim(String name, List map) throws IllegalArgumentException { + public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); - // validate map contents - if(!validateClaim(map)) { + // validate list contents + if(!validateClaim(list)) { throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); } - addClaim(name, map); + addClaim(name, list); return this; } @@ -351,7 +351,7 @@ private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { Object value = entry.getValue(); - if(value == null || !isSupported(value)) { + if(value == null || !isSupportedType(value)) { return false; } @@ -365,7 +365,7 @@ private static boolean validateClaim(Map map) { private static boolean validateClaim(List list) { // accept null values in list for (Object object : list) { - if(object != null && !isSupported(object)) { + if(object != null && !isSupportedType(object)) { return false; } } @@ -373,11 +373,11 @@ private static boolean validateClaim(List list) { } @SuppressWarnings("unchecked") - private static boolean isSupported(Object value) { + private static boolean isSupportedType(Object value) { if(value instanceof List) { return validateClaim((List)value); } else if(value instanceof Map) { - return validateClaim((Map)value); + return validateClaim((Map)value); } else { return isBasicType(value); } From 0130a265ca42530d1e53d50c5bbc6b0f9766f8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Thu, 13 Feb 2020 11:33:29 +0100 Subject: [PATCH 129/355] Improve Javadoc, use anon types where possible, add unit test for Map in List, formatting --- .../main/java/com/auth0/jwt/JWTCreator.java | 32 +++++---- .../java/com/auth0/jwt/JWTCreatorTest.java | 70 +++++++++++-------- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f4be98f7..7b5e3e77 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -297,7 +297,7 @@ public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgume * @param name the Claim's name. * @param items the Claim's value. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type + * @throws IllegalArgumentException if the name is null */ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { assertNonNull(name); @@ -307,15 +307,18 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE /** * Add a custom Map Claim with the given items. - * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types - * Boolean, Integer, Long, Double, String and Date. * + * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types + * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, + * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. + * {@linkplain List}s can contain null elements. + * * @param name the Claim's name. * @param map the Claim's key-values. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type + * @throws IllegalArgumentException if the name is null, or if the map contents does not validate. */ - public Builder withClaim(String name, Map map) throws IllegalArgumentException { + public Builder withClaim(String name, Map map) throws IllegalArgumentException { assertNonNull(name); // validate map contents if(!validateClaim(map)) { @@ -328,16 +331,18 @@ public Builder withClaim(String name, Map map) throws IllegalArg /** * Add a custom List Claim with the given items. * - * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types - * Boolean, Integer, Long, Double, String and Date. - * + * Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types + * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, + * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. + * {@linkplain List}s can contain null elements. + * * @param name the Claim's name. * @param list the Claim's list of values. * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null, or if the value is null or of an unsupported type + * @throws IllegalArgumentException if the name is null, or if the list contents does not validate. */ - public Builder withClaim(String name, List list) throws IllegalArgumentException { + public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); // validate list contents if(!validateClaim(list)) { @@ -347,9 +352,9 @@ public Builder withClaim(String name, List list) throws IllegalArgumentE return this; } - private static boolean validateClaim(Map map) { + private static boolean validateClaim(Map map) { // do not accept null values in maps - for (Entry entry : map.entrySet()) { + for (Entry entry : map.entrySet()) { Object value = entry.getValue(); if(value == null || !isSupportedType(value)) { return false; @@ -372,12 +377,11 @@ private static boolean validateClaim(List list) { return true; } - @SuppressWarnings("unchecked") private static boolean isSupportedType(Object value) { if(value instanceof List) { return validateClaim((List)value); } else if(value instanceof Map) { - return validateClaim((Map)value); + return validateClaim((Map)value); } else { return isBasicType(value); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 7871973f..7df869c1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -436,9 +436,9 @@ public void shouldAcceptCustomArrayClaimOfTypeLong() throws Exception { String[] parts = jwt.split("\\."); assertThat(parts[1], is("eyJuYW1lIjpbMSwyLDNdfQ")); } - + @Test - public void shouldAcceptCustomClaimOfTypeObject() throws Exception { + public void shouldAcceptCustomClaimOfTypeMap() throws Exception { Map data = new HashMap<>(); data.put("test1", "abc"); data.put("test2", "def"); @@ -450,25 +450,24 @@ public void shouldAcceptCustomClaimOfTypeObject() throws Exception { String[] parts = jwt.split("\\."); assertThat(parts[1], is("eyJkYXRhIjp7InRlc3QyIjoiZGVmIiwidGVzdDEiOiJhYmMifX0")); } - + @Test public void shouldRefuseCustomClaimOfTypeUserPojo() throws Exception{ - Map data = new HashMap<>(); + Map data = new HashMap<>(); data.put("test1", new UserPojo("Michael", 255)); - + exception.expect(IllegalArgumentException.class); - JWTCreator.init() + JWTCreator.init() .withClaim("pojo", data) .sign(Algorithm.HMAC256("secret")); } - - + @SuppressWarnings("unchecked") - @Test + @Test public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { Map data = new HashMap<>(); - + // simple types data.put("string", "abc"); data.put("integer", 1); @@ -476,7 +475,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { data.put("double", 123.456d); data.put("date", new Date(123L)); data.put("boolean", true); - + // array types data.put("intArray", new Integer[]{3, 5}); data.put("longArray", new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}); @@ -486,7 +485,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { Map sub = new HashMap<>(); sub.put("subKey", "subValue"); - + data.put("map", sub); String jwt = JWTCreator.init() @@ -506,7 +505,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { assertThat(map.get("double"), is(123.456d)); assertThat(map.get("date"), is(123)); assertThat(map.get("boolean"), is(true)); - + // array types assertThat(map.get("intArray"), is(Arrays.asList(new Integer[]{3, 5}))); assertThat(map.get("longArray"), is(Arrays.asList(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}))); @@ -517,12 +516,12 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { assertThat(map.get("map"), is(sub)); } - + @SuppressWarnings("unchecked") - @Test + @Test public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { List data = new ArrayList<>(); - + // simple types data.add("abc"); data.add(1); @@ -540,7 +539,7 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { Map sub = new HashMap<>(); sub.put("subKey", "subValue"); - + data.add(sub); String jwt = JWTCreator.init() @@ -571,7 +570,7 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(list.get(10), is(sub)); } - + @Test public void shouldAcceptCustomClaimForNullListItem() throws Exception{ Map data = new HashMap<>(); @@ -581,7 +580,7 @@ public void shouldAcceptCustomClaimForNullListItem() throws Exception{ .withClaim("pojo", data) .sign(Algorithm.HMAC256("secret")); } - + @Test public void shouldRefuseCustomClaimForNullMapValue() throws Exception{ Map data = new HashMap<>(); @@ -593,7 +592,7 @@ public void shouldRefuseCustomClaimForNullMapValue() throws Exception{ .withClaim("pojo", data) .sign(Algorithm.HMAC256("secret")); } - + @Test public void shouldRefuseCustomClaimForNullMapKey() throws Exception{ Map data = new HashMap<>(); @@ -604,8 +603,9 @@ public void shouldRefuseCustomClaimForNullMapKey() throws Exception{ JWTCreator.init() .withClaim("pojo", data) .sign(Algorithm.HMAC256("secret")); - } - + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void shouldRefuseCustomMapClaimForNonStringKey() throws Exception{ Map data = new HashMap<>(); @@ -617,28 +617,42 @@ public void shouldRefuseCustomMapClaimForNonStringKey() throws Exception{ .withClaim("pojo", (Map)data) .sign(Algorithm.HMAC256("secret")); } - + @Test public void shouldRefuseCustomListClaimForUnknownListElement() throws Exception{ - List list = Arrays.asList(new UserPojo("Michael", 255)); + List list = Arrays.asList(new UserPojo("Michael", 255)); + + exception.expect(IllegalArgumentException.class); + + JWTCreator.init() + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void shouldRefuseCustomListClaimForUnknownListElementWrappedInAMap() throws Exception{ + List list = Arrays.asList(new UserPojo("Michael", 255)); + Map data = new HashMap<>(); + data.put("someList", list); + exception.expect(IllegalArgumentException.class); JWTCreator.init() .withClaim("list", list) .sign(Algorithm.HMAC256("secret")); } - + @Test public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception{ - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(new Object[] {"test"}); - + exception.expect(IllegalArgumentException.class); JWTCreator.init() .withClaim("list", list) .sign(Algorithm.HMAC256("secret")); } - + } \ No newline at end of file From 373bb6bbecfca03c2981fef0714e865be55d9006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Mon, 20 Aug 2018 17:09:11 +0200 Subject: [PATCH 130/355] Support verification of Long[] datatype like in JWTCreator --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 15 +++++++++++++++ .../com/auth0/jwt/interfaces/Verification.java | 13 ++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index ff25db09..67ab80b1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -168,6 +168,21 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal requireClaim(name, items); return this; } + + /** + * Require a specific Array Claim to contain at least the given items. + * + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + @Override + public Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, items); + return this; + } @Override public JWTVerifier build() { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 511e59b7..f4fd9d8b 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -160,15 +160,18 @@ public interface Verification { */ Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; - /** - * Skip the Issued At ("iat") date verification. By default, the verification is performed. - */ - Verification ignoreIssuedAt(); - /** * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. * * @return a new JWTVerifier instance. */ + Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException; + + + /** + * Skip the Issued At ("iat") date verification. By default, the verification is performed. + */ + Verification ignoreIssuedAt(); + JWTVerifier build(); } From 9f5cb2606167e5597038ef40c773aaed53deb5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Tue, 29 Jan 2019 14:36:59 +0100 Subject: [PATCH 131/355] Add unit tests and handling of integer vs long datatypes --- .../main/java/com/auth0/jwt/JWTVerifier.java | 18 ++++++++++- .../java/com/auth0/jwt/JWTVerifierTest.java | 32 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 67ab80b1..8a2598f0 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -325,7 +325,23 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { } else if (value instanceof Date) { isValid = value.equals(claim.asDate()); } else if (value instanceof Object[]) { - List claimArr = Arrays.asList(claim.as(Object[].class)); + List claimArr; + Object[] claimAsObject = claim.as(Object[].class); + + // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. + if(value instanceof Long[]) { + // convert Integers to Longs for comparison with equals + claimArr = new ArrayList<>(claimAsObject.length); + for(Object cao : claimAsObject) { + if(cao instanceof Integer) { + claimArr.add(((Integer)cao).longValue()); + } else { + claimArr.add(cao); + } + } + } else { + claimArr = Arrays.asList(claim.as(Object[].class)); + } List valueArr = Arrays.asList((Object[]) value); isValid = claimArr.containsAll(valueArr); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index e297c59c..8d2a621c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -326,6 +326,38 @@ public void shouldValidateCustomArrayClaimOfTypeInteger() throws Exception { assertThat(jwt, is(notNullValue())); } + @Test + public void shouldValidateCustomArrayClaimOfTypeLong() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbNTAwMDAwMDAwMDAxLDUwMDAwMDAwMDAwMiw1MDAwMDAwMDAwMDNdfQ.vzV7S0gbV9ZAVxChuIt4XZuSVTxMH536rFmoHzxmayM"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("name", 500000000001L, 500000000002L, 500000000003L) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsInteger() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("name", 1L, 2L, 3L) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsIntegerAndLong() throws Exception { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSw1MDAwMDAwMDAwMDIsNTAwMDAwMDAwMDAzXX0.PQjb2rPPpYjM2sItZEzZcjS2YbfPCp6xksTSPjpjTQA"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("name", 1L, 500000000002L, 500000000003L) + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + } // Generic Delta @SuppressWarnings("RedundantCast") @Test From 5fa3fe310f69fd31a4349f352a9a87218bf56edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Skj=C3=B8lberg?= Date: Fri, 14 Feb 2020 18:53:54 +0100 Subject: [PATCH 132/355] Fix Javadoc --- .../com/auth0/jwt/interfaces/Verification.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index f4fd9d8b..dec868fb 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -161,17 +161,25 @@ public interface Verification { Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; /** - * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * Require a specific Array Claim to contain at least the given items. * - * @return a new JWTVerifier instance. + * @param name the Claim's name. + * @param items the items the Claim must contain. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. */ + Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException; - /** * Skip the Issued At ("iat") date verification. By default, the verification is performed. */ Verification ignoreIssuedAt(); + /** + * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. + * + * @return a new JWTVerifier instance. + */ JWTVerifier build(); } From cbe8eebb53c7c0519a46b061b471069ce59cee88 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 14 Feb 2020 17:06:26 -0300 Subject: [PATCH 133/355] fix NPE when missing expected array claim --- .../main/java/com/auth0/jwt/JWTVerifier.java | 18 ++++++------- .../java/com/auth0/jwt/JWTVerifierTest.java | 26 +++++++++++++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 8a2598f0..b9f745e9 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -168,7 +168,7 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal requireClaim(name, items); return this; } - + /** * Require a specific Array Claim to contain at least the given items. * @@ -178,11 +178,11 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal * @throws IllegalArgumentException if the name is null. */ @Override - public Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException { + public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); requireClaim(name, items); return this; - } + } @Override public JWTVerifier build() { @@ -220,7 +220,7 @@ private void addLeewayToDateClaims() { if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); } - if(ignoreIssuedAt) { + if (ignoreIssuedAt) { claims.remove(PublicClaims.ISSUED_AT); return; } @@ -329,18 +329,18 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { Object[] claimAsObject = claim.as(Object[].class); // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. - if(value instanceof Long[]) { + if (value instanceof Long[]) { // convert Integers to Longs for comparison with equals claimArr = new ArrayList<>(claimAsObject.length); - for(Object cao : claimAsObject) { - if(cao instanceof Integer) { - claimArr.add(((Integer)cao).longValue()); + for (Object cao : claimAsObject) { + if (cao instanceof Integer) { + claimArr.add(((Integer) cao).longValue()); } else { claimArr.add(cao); } } } else { - claimArr = Arrays.asList(claim.as(Object[].class)); + claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); } List valueArr = Arrays.asList((Object[]) value); isValid = claimArr.containsAll(valueArr); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 8d2a621c..b635e08b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -170,6 +170,28 @@ public void shouldThrowOnNullCustomClaimName() throws Exception { .withClaim(null, "value"); } + @Test + public void shouldThrowWhenExpectedArrayClaimIsMissing() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'missing' value doesn't match the required one."); + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("missing", 1, 2, 3) + .build() + .verify(token); + } + + @Test + public void shouldThrowWhenExpectedClaimIsMissing() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'missing' value doesn't match the required one."); + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("missing", "text") + .build() + .verify(token); + } + @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeString() throws Exception { exception.expect(InvalidClaimException.class); @@ -546,8 +568,8 @@ public void shouldThrowOnNegativeNotBeforeLeeway() throws Exception { .acceptNotBefore(-1); } -// Issued At with future date - @Test (expected = InvalidClaimException.class) + // Issued At with future date + @Test(expected = InvalidClaimException.class) public void shouldThrowOnFutureIssuedAt() throws Exception { Clock clock = mock(Clock.class); when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); From 8e02a2efb466ca743dc10f7f746ffbd36d6c465b Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 14 Feb 2020 17:11:08 -0300 Subject: [PATCH 134/355] code-formatting changes --- .../main/java/com/auth0/jwt/JWTCreator.java | 46 ++++---- .../main/java/com/auth0/jwt/JWTVerifier.java | 8 -- .../com/auth0/jwt/interfaces/JWTVerifier.java | 34 +++--- .../java/com/auth0/jwt/JWTCreatorTest.java | 103 ++++++++---------- .../java/com/auth0/jwt/JWTVerifierTest.java | 1 + 5 files changed, 89 insertions(+), 103 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 7b5e3e77..3cd0b7ea 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -307,81 +307,81 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE /** * Add a custom Map Claim with the given items. - * + *

* Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. * {@linkplain List}s can contain null elements. * - * @param name the Claim's name. - * @param map the Claim's key-values. + * @param name the Claim's name. + * @param map the Claim's key-values. * @return this same Builder instance. * @throws IllegalArgumentException if the name is null, or if the map contents does not validate. */ public Builder withClaim(String name, Map map) throws IllegalArgumentException { assertNonNull(name); // validate map contents - if(!validateClaim(map)) { + if (!validateClaim(map)) { throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); } addClaim(name, map); return this; - } - + } + /** * Add a custom List Claim with the given items. - * + *

* Accepted nested types are {@linkplain Map} and {@linkplain List} with basic types * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. * {@linkplain List}s can contain null elements. * - * @param name the Claim's name. + * @param name the Claim's name. * @param list the Claim's list of values. * @return this same Builder instance. * @throws IllegalArgumentException if the name is null, or if the list contents does not validate. */ - + public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); // validate list contents - if(!validateClaim(list)) { + if (!validateClaim(list)) { throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); } addClaim(name, list); return this; - } + } private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { Object value = entry.getValue(); - if(value == null || !isSupportedType(value)) { + if (value == null || !isSupportedType(value)) { return false; } - - if(entry.getKey() == null || !(entry.getKey() instanceof String)) { + + if (entry.getKey() == null || !(entry.getKey() instanceof String)) { return false; } } return true; } - + private static boolean validateClaim(List list) { // accept null values in list for (Object object : list) { - if(object != null && !isSupportedType(object)) { + if (object != null && !isSupportedType(object)) { return false; } } return true; - } + } private static boolean isSupportedType(Object value) { - if(value instanceof List) { - return validateClaim((List)value); - } else if(value instanceof Map) { - return validateClaim((Map)value); + if (value instanceof List) { + return validateClaim((List) value); + } else if (value instanceof Map) { + return validateClaim((Map) value); } else { return isBasicType(value); } @@ -389,8 +389,8 @@ private static boolean isSupportedType(Object value) { private static boolean isBasicType(Object value) { Class c = value.getClass(); - - if(c.isArray()) { + + if (c.isArray()) { return c == Integer[].class || c == Long[].class || c == String[].class; } return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Boolean.class; diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index b9f745e9..0f56b3da 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -169,14 +169,6 @@ public Verification withArrayClaim(String name, Integer... items) throws Illegal return this; } - /** - * Require a specific Array Claim to contain at least the given items. - * - * @param name the Claim's name. - * @param items the items the Claim must contain. - * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. - */ @Override public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index 140af8e6..a335a83e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -4,22 +4,22 @@ public interface JWTVerifier { - - /** - * Performs the verification against the given Token - * - * @param token to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException if any of the verification steps fail - */ - DecodedJWT verify(String token) throws JWTVerificationException; - /** - * Performs the verification against the given decoded JWT - * - * @param jwt to verify. - * @return a verified and decoded JWT. - * @throws JWTVerificationException if any of the verification steps fail - */ - DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException; + /** + * Performs the verification against the given Token + * + * @param token to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the verification steps fail + */ + DecodedJWT verify(String token) throws JWTVerificationException; + + /** + * Performs the verification against the given decoded JWT + * + * @param jwt to verify. + * @return a verified and decoded JWT. + * @throws JWTVerificationException if any of the verification steps fail + */ + DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException; } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 7df869c1..0ab088c2 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import com.fasterxml.jackson.databind.ObjectMapper; - import org.apache.commons.codec.binary.Base64; import org.junit.Rule; import org.junit.Test; @@ -14,13 +13,7 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateKey; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -63,8 +56,8 @@ public void shouldAddHeaderClaim() throws Exception { @Test public void shouldReturnBuilderIfNullMapIsProvided() throws Exception { String signed = JWTCreator.init() - .withHeader(null) - .sign(Algorithm.HMAC256("secret")); + .withHeader(null) + .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); } @@ -75,9 +68,9 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() throws header.put(PublicClaims.KEY_ID, "xyz"); String signed = JWTCreator.init() - .withKeyId("abc") - .withHeader(header) - .sign(Algorithm.HMAC256("secret")); + .withKeyId("abc") + .withHeader(header) + .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); @@ -91,9 +84,9 @@ public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() throws Exce header.put(PublicClaims.KEY_ID, "xyz"); String signed = JWTCreator.init() - .withHeader(header) - .withKeyId("abc") - .sign(Algorithm.HMAC256("secret")); + .withHeader(header) + .withKeyId("abc") + .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); @@ -108,9 +101,9 @@ public void shouldRemoveHeaderIfTheValueIsNull() throws Exception { header.put("test2", "isSet"); String signed = JWTCreator.init() - .withKeyId("test") - .withHeader(header) - .sign(Algorithm.HMAC256("secret")); + .withKeyId("test") + .withHeader(header) + .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); @@ -452,15 +445,15 @@ public void shouldAcceptCustomClaimOfTypeMap() throws Exception { } @Test - public void shouldRefuseCustomClaimOfTypeUserPojo() throws Exception{ + public void shouldRefuseCustomClaimOfTypeUserPojo() throws Exception { Map data = new HashMap<>(); data.put("test1", new UserPojo("Michael", 255)); exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); } @SuppressWarnings("unchecked") @@ -494,7 +487,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class).get("data"); @@ -529,7 +522,7 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { data.add(123.456d); data.add(new Date(123L)); data.add(true); - + // array types data.add(new Integer[]{3, 5}); data.add(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}); @@ -548,18 +541,18 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); List list = (List) mapper.readValue(body, Map.class).get("data"); - + assertThat(list.get(0), is("abc")); assertThat(list.get(1), is(1)); assertThat(list.get(2), is(Long.MAX_VALUE)); assertThat(list.get(3), is(123.456d)); assertThat(list.get(4), is(123)); assertThat(list.get(5), is(true)); - + // array types assertThat(list.get(6), is(Arrays.asList(new Integer[]{3, 5}))); assertThat(list.get(7), is(Arrays.asList(new Long[]{Long.MAX_VALUE, Long.MIN_VALUE}))); @@ -572,87 +565,87 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { } @Test - public void shouldAcceptCustomClaimForNullListItem() throws Exception{ + public void shouldAcceptCustomClaimForNullListItem() throws Exception { Map data = new HashMap<>(); data.put("test1", Arrays.asList("a", null, "c")); - + JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); } @Test - public void shouldRefuseCustomClaimForNullMapValue() throws Exception{ + public void shouldRefuseCustomClaimForNullMapValue() throws Exception { Map data = new HashMap<>(); data.put("subKey", null); - + exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); } @Test - public void shouldRefuseCustomClaimForNullMapKey() throws Exception{ + public void shouldRefuseCustomClaimForNullMapKey() throws Exception { Map data = new HashMap<>(); data.put(null, "subValue"); - + exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); + .withClaim("pojo", data) + .sign(Algorithm.HMAC256("secret")); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) @Test - public void shouldRefuseCustomMapClaimForNonStringKey() throws Exception{ + public void shouldRefuseCustomMapClaimForNonStringKey() throws Exception { Map data = new HashMap<>(); data.put(new Object(), "value"); - + exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("pojo", (Map)data) - .sign(Algorithm.HMAC256("secret")); + .withClaim("pojo", (Map) data) + .sign(Algorithm.HMAC256("secret")); } @Test - public void shouldRefuseCustomListClaimForUnknownListElement() throws Exception{ + public void shouldRefuseCustomListClaimForUnknownListElement() throws Exception { List list = Arrays.asList(new UserPojo("Michael", 255)); exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("list", list) - .sign(Algorithm.HMAC256("secret")); + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); } @Test - public void shouldRefuseCustomListClaimForUnknownListElementWrappedInAMap() throws Exception{ + public void shouldRefuseCustomListClaimForUnknownListElementWrappedInAMap() throws Exception { List list = Arrays.asList(new UserPojo("Michael", 255)); - + Map data = new HashMap<>(); data.put("someList", list); exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("list", list) - .sign(Algorithm.HMAC256("secret")); + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); } @Test - public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception{ + public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception { List list = new ArrayList<>(); - list.add(new Object[] {"test"}); + list.add(new Object[]{"test"}); exception.expect(IllegalArgumentException.class); JWTCreator.init() - .withClaim("list", list) - .sign(Algorithm.HMAC256("secret")); + .withClaim("list", list) + .sign(Algorithm.HMAC256("secret")); } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b635e08b..c60e31db 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -380,6 +380,7 @@ public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsIntegerAndLong() assertThat(jwt, is(notNullValue())); } + // Generic Delta @SuppressWarnings("RedundantCast") @Test From dd164c656c5deb2ca0d52883f09425bfa1f7760c Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 14 Feb 2020 17:34:12 -0300 Subject: [PATCH 135/355] Release 3.10.0 --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e03dd703..941b9c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## [3.10.0](https://github.com/auth0/java-jwt/tree/3.10.0) (2020-02-14) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.9.0...3.10.0) +**Closed issues** +- NullPointerException when the claim doesn't exist in the token [\#384](https://github.com/auth0/java-jwt/issues/384) + +**Added** +- Add Javadoc URL and badge to the README [\#382](https://github.com/auth0/java-jwt/pull/382) ([lbalmaceda](https://github.com/lbalmaceda)) +- Allow to customize the typ header claim [\#381](https://github.com/auth0/java-jwt/pull/381) ([lbalmaceda](https://github.com/lbalmaceda)) +- JWTCreator for basic types [\#282](https://github.com/auth0/java-jwt/pull/282) ([skjolber](https://github.com/skjolber)) +- Support verification of Long[] datatype like in JWTCreator [\#278](https://github.com/auth0/java-jwt/pull/278) ([skjolber](https://github.com/skjolber)) + +**Changed** +- Update to Gradle 6.1.1 [\#389](https://github.com/auth0/java-jwt/pull/389) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Handle missing expected array claim [\#393](https://github.com/auth0/java-jwt/pull/393) ([lbalmaceda](https://github.com/lbalmaceda)) +- Update tests to use valid Base64 URL-encoded tokens [\#386](https://github.com/auth0/java-jwt/pull/386) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.9.0](https://github.com/auth0/java-jwt/tree/3.9.0) (2020-01-02) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.8.3...3.9.0) diff --git a/README.md b/README.md index b63599a2..344ec66b 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.9.0 + 3.10.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.9.0' +implementation 'com.auth0:java-jwt:3.10.0' ``` ## Available Algorithms From 7718330f6155035bc43038d4ff4ccc19171a6131 Mon Sep 17 00:00:00 2001 From: Claude Gex Date: Fri, 28 Feb 2020 10:41:34 +0100 Subject: [PATCH 136/355] Update build.gradle update to jackson-databind 2.10.2 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 36faf197..7ed92c8e 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0.pr3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.2' implementation 'commons-codec:commons-codec:1.12' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From f50bacf29e98c23bdff8dd3136cc6ef86abecd5a Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 13 Mar 2020 14:42:53 -0500 Subject: [PATCH 137/355] Update dependencies (#407) --- lib/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 7ed92c8e..dcd0d615 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,8 +34,8 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.2' - implementation 'commons-codec:commons-codec:1.12' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.3' + implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' From a5da770f88101fa292760898891ff5f622b10263 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 13 Mar 2020 15:08:57 -0500 Subject: [PATCH 138/355] Release 3.10.1 (#408) --- CHANGELOG.md | 9 +++++++++ README.md | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941b9c87..d509a704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [3.10.1](https://github.com/auth0/java-jwt/tree/3.10.1) (2020-03-13) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.0...3.10.1) + +**Changed** +- Update Jackson and Commons Codec dependencies [\#407](https://github.com/auth0/java-jwt/pull/407) ([jimmyjames](https://github.com/jimmyjames)) + +**Security** +- Update jackson-databind to 2.10.2 [\#399](https://github.com/auth0/java-jwt/pull/399) ([gexclaude](https://github.com/gexclaude)) + ## [3.10.0](https://github.com/auth0/java-jwt/tree/3.10.0) (2020-02-14) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.9.0...3.10.0) **Closed issues** diff --git a/README.md b/README.md index 344ec66b..66849932 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.10.0 + 3.10.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.10.0' +implementation 'com.auth0:java-jwt:3.10.1' ``` ## Available Algorithms From 45036b98d3cd35e7540237797fecd7f89c2d3b64 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 25 Mar 2020 16:58:09 -0500 Subject: [PATCH 139/355] Check varargs null values in JWTVerifier (#412) * Check varargs null values in JWTVerifier * Review feedback - Rename method for clarity --- .../main/java/com/auth0/jwt/JWTVerifier.java | 18 +++- .../java/com/auth0/jwt/JWTVerifierTest.java | 89 ++++++++++++++++++- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 0f56b3da..24e109a4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -57,7 +57,7 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { - requireClaim(PublicClaims.ISSUER, issuer == null ? null : Arrays.asList(issuer)); + requireClaim(PublicClaims.ISSUER, isNullOrEmpty(issuer) ? null : Arrays.asList(issuer)); return this; } @@ -69,7 +69,7 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { - requireClaim(PublicClaims.AUDIENCE, audience == null ? null : Arrays.asList(audience)); + requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @@ -230,6 +230,20 @@ private void requireClaim(String name, Object value) { } } + private static boolean isNullOrEmpty(String[] args) { + if (args == null || args.length == 0) { + return true; + } + boolean isAllNull = true; + for (String arg: args) { + if (arg != null) { + isAllNull = false; + break; + } + } + return isAllNull; + } + /** * Perform the verification against the given Token, using any previous configured options. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index c60e31db..aec70c70 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -125,7 +126,7 @@ public void shouldValidateAudience() throws Exception { .verify(tokenArr); assertThat(jwtArr, is(notNullValue())); - } + } @Test public void shouldAcceptPartialAudience() throws Exception { @@ -150,12 +151,53 @@ public void shouldThrowOnInvalidAudience() throws Exception { .verify(token); } + @Test + public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAudience((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAudience() + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + String emptyAud = " "; + verifier = JWTVerifier.init(algorithm) + .withAudience(emptyAud) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + } + @Test public void shouldRemoveAudienceWhenPassingNull() throws Exception { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withAudience("John") - .withAudience(null) + .withAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAudience("John") + .withAudience((String[]) null) .build(); assertThat(verifier.claims, is(notNullValue())); @@ -674,11 +716,52 @@ public void shouldRemoveClaimWhenPassingNull() throws Exception { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withIssuer("iss") - .withIssuer(null) + .withIssuer((String) null) .build(); assertThat(verifier.claims, is(notNullValue())); assertThat(verifier.claims, not(hasKey("iss"))); + + verifier = JWTVerifier.init(algorithm) + .withIssuer("iss") + .withIssuer((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("iss"))); + } + + @Test + public void shouldRemoveIssuerWhenPassingNullReference() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withIssuer((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("iss"))); + + verifier = JWTVerifier.init(algorithm) + .withIssuer((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("iss"))); + + verifier = JWTVerifier.init(algorithm) + .withIssuer() + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("iss"))); + + String emptyIss = " "; + verifier = JWTVerifier.init(algorithm) + .withIssuer(emptyIss) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, hasEntry("iss", Collections.singletonList(emptyIss))); } @Test From f2d95fa7bf1880506680262bcff6e12673dbb133 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 27 Mar 2020 14:05:11 -0500 Subject: [PATCH 140/355] JavaDoc fix (#413) --- lib/src/main/java/com/auth0/jwt/interfaces/Verification.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index dec868fb..72ae35d2 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -173,6 +173,8 @@ public interface Verification { /** * Skip the Issued At ("iat") date verification. By default, the verification is performed. + * + * @return this same Verification instance. */ Verification ignoreIssuedAt(); From 720ba889c83cb0d1e92c7bfefd191a1e8a498bae Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 27 Mar 2020 14:18:32 -0500 Subject: [PATCH 141/355] Release 3.10.2 --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d509a704..acb7bc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [3.10.2](https://github.com/auth0/java-jwt/tree/3.10.2) (2020-03-27) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.1...3.10.2) + +**Fixed** +- JavaDoc fix [\#413](https://github.com/auth0/java-jwt/pull/413) ([jimmyjames](https://github.com/jimmyjames)) +- Check varargs null values in JWTVerifier [\#412](https://github.com/auth0/java-jwt/pull/412) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.10.1](https://github.com/auth0/java-jwt/tree/3.10.1) (2020-03-13) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.0...3.10.1) diff --git a/README.md b/README.md index 66849932..e466d2dd 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.10.1 + 3.10.2 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.10.1' +implementation 'com.auth0:java-jwt:3.10.2' ``` ## Available Algorithms From a81f3076ee02d5bfa305597b5d2aa2341c2657a8 Mon Sep 17 00:00:00 2001 From: danila_varatyntsev Date: Thu, 23 Apr 2020 10:58:11 +0300 Subject: [PATCH 142/355] Fixed an NPE on null map and list claims --- .../main/java/com/auth0/jwt/JWTCreator.java | 4 +-- .../java/com/auth0/jwt/JWTCreatorTest.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 3cd0b7ea..0b4da54b 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -321,7 +321,7 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE public Builder withClaim(String name, Map map) throws IllegalArgumentException { assertNonNull(name); // validate map contents - if (!validateClaim(map)) { + if (map != null && !validateClaim(map)) { throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); } addClaim(name, map); @@ -345,7 +345,7 @@ public Builder withClaim(String name, Map map) throws IllegalArgument public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); // validate list contents - if (!validateClaim(list)) { + if (list != null && !validateClaim(list)) { throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); } addClaim(name, list); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 0ab088c2..c8dcae8d 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -15,6 +15,7 @@ import java.security.interfaces.RSAPrivateKey; import java.util.*; +import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; @@ -574,6 +575,40 @@ public void shouldAcceptCustomClaimForNullListItem() throws Exception { .sign(Algorithm.HMAC256("secret")); } + @Test + @SuppressWarnings("unchecked") + public void shouldAcceptCustomClaimWithNullMapAndRemoveClaim() throws Exception { + String jwt = JWTCreator.init() + .withClaim("map", "stubValue") + .withClaim("map", (Map) null) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + Map map = (Map) mapper.readValue(body, Map.class); + assertThat(map, anEmptyMap()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldAcceptCustomClaimWithNullListAndRemoveClaim() throws Exception { + String jwt = JWTCreator.init() + .withClaim("list", "stubValue") + .withClaim("list", (List) null) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + Map map = (Map) mapper.readValue(body, Map.class); + assertThat(map, anEmptyMap()); + } + @Test public void shouldRefuseCustomClaimForNullMapValue() throws Exception { Map data = new HashMap<>(); From 79ed2431f0f3b35bff1e1a461ba1a240ca4aa8f2 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 24 Apr 2020 14:24:56 -0500 Subject: [PATCH 143/355] Release 3.10.3 (#418) --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb7bc52..21095ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.10.3](https://github.com/auth0/java-jwt/tree/3.10.3) (2020-04-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.2...3.10.3) + +**Fixed** +- Fixed an NPE on null map and list claims [\#417](https://github.com/auth0/java-jwt/pull/417) ([Vorotyntsev](https://github.com/Vorotyntsev)) + ## [3.10.2](https://github.com/auth0/java-jwt/tree/3.10.2) (2020-03-27) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.1...3.10.2) diff --git a/README.md b/README.md index e466d2dd..a0685729 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.10.2 + 3.10.3 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.10.2' +implementation 'com.auth0:java-jwt:3.10.3' ``` ## Available Algorithms From b3dd755ae208ffa9d532643d0454cb80b1f61b33 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 17:36:50 -0300 Subject: [PATCH 144/355] make PayloadImpl keep an unmodifiable list of audiences --- .../main/java/com/auth0/jwt/impl/PayloadImpl.java | 2 +- .../java/com/auth0/jwt/impl/PayloadImplTest.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index 2c5558f2..cf678f81 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -30,7 +30,7 @@ class PayloadImpl implements Payload, Serializable { PayloadImpl(String issuer, String subject, List audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map tree, ObjectReader objectReader) { this.issuer = issuer; this.subject = subject; - this.audience = audience; + this.audience = audience != null ? Collections.unmodifiableList(audience) : null; this.expiresAt = expiresAt; this.notBefore = notBefore; this.issuedAt = issuedAt; diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index 56654427..3c604a30 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -13,10 +13,7 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,7 +36,7 @@ public class PayloadImplTest { public void setUp() throws Exception { mapper = getDefaultObjectMapper(); objectReader = mapper.reader(); - + expiresAt = Mockito.mock(Date.class); notBefore = Mockito.mock(Date.class); issuedAt = Mockito.mock(Date.class); @@ -56,6 +53,13 @@ public void shouldHaveUnmodifiableTree() throws Exception { payload.getTree().put("something", null); } + @Test + public void shouldHaveUnmodifiableAudience() throws Exception { + exception.expect(UnsupportedOperationException.class); + PayloadImpl payload = new PayloadImpl(null, null, new ArrayList(), null, null, null, null, null, objectReader); + payload.getAudience().add("something"); + } + @Test public void shouldGetIssuer() throws Exception { assertThat(payload, is(notNullValue())); From 4d2bfdbff34bc76c9793931d0527a75f9b8216be Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:02:07 -0300 Subject: [PATCH 145/355] add failing test case for date reference manipulation --- .../java/com/auth0/jwt/JWTVerifierTest.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index aec70c70..d57ca187 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -15,10 +15,10 @@ import java.util.HashMap; import java.util.Map; +import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.*; 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 JWTVerifierTest { @@ -126,7 +126,7 @@ public void shouldValidateAudience() throws Exception { .verify(tokenArr); assertThat(jwtArr, is(notNullValue())); - } + } @Test public void shouldAcceptPartialAudience() throws Exception { @@ -505,6 +505,23 @@ public void shouldThrowOnNegativeCustomLeeway() throws Exception { .acceptLeeway(-1); } + @Test + public void shouldNotModifyOriginalClockDateWhenVerifying() throws Exception { + Clock clock = mock(Clock.class); + Date clockDate = spy(new Date(DATE_TOKEN_MS_VALUE)); + when(clock.getToday()).thenReturn(clockDate); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + JWTVerifier verifier = verification + .build(clock); + + DecodedJWT jwt = verifier.verify(token); + assertThat(jwt, is(notNullValue())); + + verify(clockDate, never()).setTime(anyLong()); + } + // Expires At @Test public void shouldValidateExpiresAtWithLeeway() throws Exception { From 7d0111cf247ff5e8828f67dc837ac1ccdd056f1b Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:02:29 -0300 Subject: [PATCH 146/355] fix date manipulation by creating a new Date instance --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 24e109a4..a5fab486 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -235,7 +235,7 @@ private static boolean isNullOrEmpty(String[] args) { return true; } boolean isAllNull = true; - for (String arg: args) { + for (String arg : args) { if (arg != null) { isAllNull = false; break; @@ -364,7 +364,7 @@ private void assertValidStringClaim(String claimName, String value, String expec } private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); + Date today = new Date(clock.getToday().getTime()); today.setTime(today.getTime() / 1000 * 1000); // truncate millis if (shouldBeFuture) { assertDateIsFuture(date, leeway, today); From 3d3c794f3dbe1513106b09143b1ae12c91dda1ca Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:10:03 -0300 Subject: [PATCH 147/355] add failing test case for array manipulation in HMACAlgorithm --- .../com/auth0/jwt/algorithms/HMACAlgorithmTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index b70c8633..22d29258 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -9,6 +9,7 @@ import org.junit.rules.ExpectedException; import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -39,6 +40,18 @@ public void shouldGetStringBytes() throws Exception { assertTrue(Arrays.equals(expectedBytes, HMACAlgorithm.getSecretBytes(text))); } + @Test + public void shouldKeepCopyTheReceivedSecretArray() throws Exception { + String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + byte[] secretArray = "secret".getBytes(Charset.defaultCharset()); + Algorithm algorithmString = Algorithm.HMAC256(secretArray); + + DecodedJWT decoded = JWT.decode(jwt); + algorithmString.verify(decoded); + secretArray[0] = secretArray[1]; + algorithmString.verify(decoded); + } + @Test public void shouldPassHMAC256Verification() throws Exception { String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; From b161fc4dbf33dd8fb04541e898a7ae9e18b1f3a9 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:11:54 -0300 Subject: [PATCH 148/355] fix array manipulation by making a copy of the received value --- lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 5df23a83..1bdb2f5d 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -8,6 +8,7 @@ import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; class HMACAlgorithm extends Algorithm { @@ -20,7 +21,7 @@ class HMACAlgorithm extends Algorithm { if (secretBytes == null) { throw new IllegalArgumentException("The Secret cannot be null"); } - this.secret = secretBytes; + this.secret = Arrays.copyOf(secretBytes, secretBytes.length); this.crypto = crypto; } From 542f7fa543270f4434bc7747a13cc17bf9824ce3 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:19:16 -0300 Subject: [PATCH 149/355] use final modifier on a field param --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 0b4da54b..5fa1fe07 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -59,7 +59,7 @@ static JWTCreator.Builder init() { */ public static class Builder { private final Map payloadClaims; - private Map headerClaims; + private final Map headerClaims; Builder() { this.payloadClaims = new HashMap<>(); From 54286fccd3234cc1a458236d912e3c48f1f94164 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 18:42:07 -0300 Subject: [PATCH 150/355] document java thread-safety --- .../main/java/com/auth0/jwt/ClockImpl.java | 8 +++++ .../main/java/com/auth0/jwt/JWTCreator.java | 2 ++ .../main/java/com/auth0/jwt/JWTDecoder.java | 2 ++ .../main/java/com/auth0/jwt/JWTVerifier.java | 2 ++ .../com/auth0/jwt/algorithms/Algorithm.java | 29 ++++++++++--------- .../auth0/jwt/algorithms/CryptoHelper.java | 6 +++- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 7 ++++- .../auth0/jwt/algorithms/HMACAlgorithm.java | 5 ++++ .../auth0/jwt/algorithms/RSAAlgorithm.java | 5 ++++ .../auth0/jwt/impl/HeaderDeserializer.java | 11 +++++-- .../auth0/jwt/impl/PayloadDeserializer.java | 11 +++++-- .../java/com/auth0/jwt/impl/PayloadImpl.java | 6 +++- .../com/auth0/jwt/impl/PayloadSerializer.java | 13 +++++++-- 13 files changed, 83 insertions(+), 24 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/ClockImpl.java b/lib/src/main/java/com/auth0/jwt/ClockImpl.java index 45e3edfc..29ed3c1e 100644 --- a/lib/src/main/java/com/auth0/jwt/ClockImpl.java +++ b/lib/src/main/java/com/auth0/jwt/ClockImpl.java @@ -4,6 +4,14 @@ import java.util.Date; +/** + * Default Clock implementation used for verification. + * + * @see Clock + * @see JWTVerifier + *

+ * This class is thread-safe. + */ final class ClockImpl implements Clock { ClockImpl() { diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 5fa1fe07..bd0dff9d 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -21,6 +21,8 @@ /** * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) from a given Header and Payload content. + *

+ * This class is thread-safe. */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index b14c2ac3..ffd0217c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -16,6 +16,8 @@ /** * The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation. + *

+ * This class is thread-safe. */ @SuppressWarnings("WeakerAccess") final class JWTDecoder implements DecodedJWT, Serializable { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index a5fab486..487addaf 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -13,6 +13,8 @@ /** * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. + *

+ * This class is thread-safe. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 24ad025b..1a8c6c2d 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -6,11 +6,12 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.io.ByteArrayOutputStream; import java.security.interfaces.*; /** * The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token. + *

+ * This class and its subclasses are thread-safe. */ @SuppressWarnings("WeakerAccess") public abstract class Algorithm { @@ -137,7 +138,7 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { * * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC256 Algorithm. - * @throws IllegalArgumentException if the provided Secret is null. + * @throws IllegalArgumentException if the provided Secret is null. */ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS256", "HmacSHA256", secret); @@ -148,7 +149,7 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { * * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC384 Algorithm. - * @throws IllegalArgumentException if the provided Secret is null. + * @throws IllegalArgumentException if the provided Secret is null. */ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS384", "HmacSHA384", secret); @@ -159,7 +160,7 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { * * @param secret the secret to use in the verify or signing instance. * @return a valid HMAC512 Algorithm. - * @throws IllegalArgumentException if the provided Secret is null. + * @throws IllegalArgumentException if the provided Secret is null. */ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS512", "HmacSHA512", secret); @@ -365,20 +366,20 @@ public String toString() { /** * Sign the given content using this Algorithm instance. * - * @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature. + * @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature. * @param payloadBytes an array of bytes representing the base64 encoded payload content to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. */ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException { - // default implementation; keep around until sign(byte[]) method is removed - byte[] contentBytes = new byte[headerBytes.length + 1 + payloadBytes.length]; - - System.arraycopy(headerBytes, 0, contentBytes, 0, headerBytes.length); - contentBytes[headerBytes.length] = (byte)'.'; - System.arraycopy(payloadBytes, 0, contentBytes, headerBytes.length + 1, payloadBytes.length); - - return sign(contentBytes); + // default implementation; keep around until sign(byte[]) method is removed + byte[] contentBytes = new byte[headerBytes.length + 1 + payloadBytes.length]; + + System.arraycopy(headerBytes, 0, contentBytes, 0, headerBytes.length); + contentBytes[headerBytes.length] = (byte) '.'; + System.arraycopy(payloadBytes, 0, contentBytes, headerBytes.length + 1, payloadBytes.length); + + return sign(contentBytes); } /** @@ -389,7 +390,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene * @throws SignatureGenerationException if the Key is invalid. * @deprecated Please use the {@linkplain #sign(byte[], byte[])} method instead. */ - + @Deprecated public abstract byte[] sign(byte[] contentBytes) throws SignatureGenerationException; diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java index dc92ff97..62aec632 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java @@ -2,10 +2,14 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; - import java.nio.charset.StandardCharsets; import java.security.*; +/** + * Class used to perform the signature hash calculations. + *

+ * This class is thread-safe. + */ class CryptoHelper { private static final byte JWT_PART_SEPARATOR = (byte)46; 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 12ddca70..6d065c9c 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -12,6 +12,11 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +/** + * Subclass representing an Elliptic Curve signing algorithm + *

+ * This class is thread-safe. + */ class ECDSAAlgorithm extends Algorithm { private final ECDSAKeyProvider keyProvider; @@ -65,7 +70,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene throw new SignatureGenerationException(this, e); } } - + @Override @Deprecated public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 1bdb2f5d..596f907e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -10,6 +10,11 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; +/** + * Subclass representing an Hash-based MAC signing algorithm + *

+ * This class is thread-safe. + */ class HMACAlgorithm extends Algorithm { private final CryptoHelper crypto; diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 15cce55a..a61fc97e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -13,6 +13,11 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +/** + * Subclass representing an RSA signing algorithm + *

+ * This class is thread-safe. + */ class RSAAlgorithm extends Algorithm { private final RSAKeyProvider keyProvider; diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index cea2944a..6bba2caa 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -11,17 +11,24 @@ import java.io.IOException; import java.util.Map; +/** + * Jackson deserializer implementation for converting from JWT Header parts. + * + * @see JWTParser + *

+ * This class is thread-safe. + */ class HeaderDeserializer extends StdDeserializer { private final ObjectReader objectReader; - + HeaderDeserializer(ObjectReader objectReader) { this(null, objectReader); } private HeaderDeserializer(Class vc, ObjectReader objectReader) { super(vc); - + this.objectReader = objectReader; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 0c9de6c7..8935a277 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -13,17 +13,24 @@ import java.io.IOException; import java.util.*; +/** + * Jackson deserializer implementation for converting from JWT Payload parts. + * + * @see JWTParser + *

+ * This class is thread-safe. + */ class PayloadDeserializer extends StdDeserializer { private final ObjectReader objectReader; - + PayloadDeserializer(ObjectReader reader) { this(null, reader); } private PayloadDeserializer(Class vc, ObjectReader reader) { super(vc); - + this.objectReader = reader; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index cf678f81..f056c038 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -11,7 +11,11 @@ import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; /** - * The PayloadImpl class implements the Payload interface. + * Decoder of string JSON Web Tokens into their POJO representations. + * + * @see Payload + *

+ * This class is thread-safe. */ class PayloadImpl implements Payload, Serializable { diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index dd0d2f42..684137cc 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -8,6 +8,13 @@ import java.util.Date; import java.util.Map; +/** + * Jackson serializer implementation for converting into JWT Payload parts. + * + * @see com.auth0.jwt.JWTCreator + *

+ * This class is thread-safe. + */ public class PayloadSerializer extends StdSerializer { public PayloadSerializer() { @@ -20,14 +27,14 @@ private PayloadSerializer(Class t) { @Override public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider provider) throws IOException { - + gen.writeStartObject(); for (Map.Entry e : holder.getClaims().entrySet()) { switch (e.getKey()) { case PublicClaims.AUDIENCE: if (e.getValue() instanceof String) { gen.writeFieldName(e.getKey()); - gen.writeString((String)e.getValue()); + gen.writeString((String) e.getValue()); break; } String[] audArray = (String[]) e.getValue(); @@ -37,7 +44,7 @@ public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider } else if (audArray.length > 1) { gen.writeFieldName(e.getKey()); gen.writeStartArray(); - for(String aud : audArray) { + for (String aud : audArray) { gen.writeString(aud); } gen.writeEndArray(); From 9fdf105c65cded2a4fde33facf24db6f581e5c08 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 16 Jun 2020 13:46:04 -0300 Subject: [PATCH 151/355] Update lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java Co-authored-by: Rita Zerrizuela --- .../test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 22d29258..38aaf301 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -41,7 +41,7 @@ public void shouldGetStringBytes() throws Exception { } @Test - public void shouldKeepCopyTheReceivedSecretArray() throws Exception { + public void shouldCopyTheReceivedSecretArray() throws Exception { String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; byte[] secretArray = "secret".getBytes(Charset.defaultCharset()); Algorithm algorithmString = Algorithm.HMAC256(secretArray); @@ -293,4 +293,4 @@ public void shouldBeEqualSignatureMethodResults() throws Exception { assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); } -} \ No newline at end of file +} From 5dbe4e84eef0422720f504cb10a4ec03e87cf2e8 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 12 Jun 2020 16:39:50 -0300 Subject: [PATCH 152/355] wrap IllegalArgumentException into JWTDecodeException --- lib/src/main/java/com/auth0/jwt/JWTDecoder.java | 2 ++ lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index ffd0217c..445bf95d 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -41,6 +41,8 @@ final class JWTDecoder implements DecodedJWT, Serializable { payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1])); } catch (NullPointerException e) { throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e); + } catch (IllegalArgumentException e){ + throw new JWTDecodeException("The input is not a valid base 64 encoded string.", e); } header = converter.parseHeader(headerJson); payload = converter.parsePayload(payloadJson); diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index dfe59182..efd1b398 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -32,6 +32,13 @@ public void getSubject() throws Exception { } // Exceptions + @Test + public void shouldThrowIfTheContentIsNotProperlyEncoded() throws Exception { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The input is not a valid base 64 encoded string."); + JWT.decode("eyJ0eXAiOiJKV1QiLCJhbGciO-corrupted.eyJ0ZXN0IjoxMjN9.sLtFC2rLAzN0-UJ13OLQX6ezNptAQzespaOGwCnpqk"); + } + @Test public void shouldThrowIfLessThan3Parts() throws Exception { exception.expect(JWTDecodeException.class); From 984eaaf0fb74cba344d3f743aa0fa0d0724edcec Mon Sep 17 00:00:00 2001 From: fossabot Date: Wed, 9 Sep 2020 10:59:06 -0700 Subject: [PATCH 153/355] Add license scan report and status Signed off by: fossabot --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a0685729..70d6f49c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ - - # Java JWT [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) [![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_shield) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -433,3 +432,6 @@ If you have found a bug or if you have a feature request, please report them at ## License This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. + + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_large) \ No newline at end of file From 027ff9516d2864900810295c32710e5323a6f3fa Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 14 Sep 2020 17:27:25 -0500 Subject: [PATCH 154/355] PR 428 original changes --- README.md | 1 + lib/README-ES256K.txt | 21 ++++ .../com/auth0/jwt/algorithms/Algorithm.java | 45 ++++++- .../com/auth0/jwt/ConcurrentVerifyTest.java | 15 ++- .../java/com/auth0/jwt/JWTCreatorTest.java | 39 +++++- lib/src/test/java/com/auth0/jwt/JWTTest.java | 20 ++- lib/src/test/java/com/auth0/jwt/PemUtils.java | 8 +- .../auth0/jwt/algorithms/AlgorithmTest.java | 45 +++++++ .../jwt/algorithms/ECDSAAlgorithmTest.java | 114 ++++++++++++++++- .../ECDSABouncyCastleProviderTests.java | 116 +++++++++++++++++- lib/src/test/resources/ec256k-key-private.pem | 5 + .../resources/ec256k-key-public-invalid.pem | 4 + lib/src/test/resources/ec256k-key-public.pem | 4 + 13 files changed, 422 insertions(+), 15 deletions(-) create mode 100644 lib/README-ES256K.txt create mode 100644 lib/src/test/resources/ec256k-key-private.pem create mode 100644 lib/src/test/resources/ec256k-key-public-invalid.pem create mode 100644 lib/src/test/resources/ec256k-key-public.pem diff --git a/README.md b/README.md index 70d6f49c..1cdbe422 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ The library implements JWT Verification and Signing using the following algorith | RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 | | RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 | | ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 | +| ES256K | ECDSA256 | ECDSA with curve secp256k1 and SHA-256 | | ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 | | ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | diff --git a/lib/README-ES256K.txt b/lib/README-ES256K.txt new file mode 100644 index 00000000..02fdf82e --- /dev/null +++ b/lib/README-ES256K.txt @@ -0,0 +1,21 @@ + +How to create a secp256k1 key to be used with ES256K algorithm: + +## first we generate the key +openssl ecparam -genkey -name secp256k1 -out KEY.pem -text +## the file is split into 2 parts a curve specification (EC PARAMETERS) and the key value +## this format is not suitable to be read by the PermUtils PEM reader so we have to convert it +## into PCKS8 format: + +openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in KEY.pem -out ec256k-key.private.pem + +## the public key is then calculated as follows: +openssl ec -pubout -in KEY.pem -out ec256k-key-public.pem + +## +## how to get DER signature +ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); +String[] parts = ES256K_JWT.split("\\."); + +byte[] derSignature = algorithm256.JOSEToDER(Base64.decodeBase64(parts[2])); +String jwt=parts[0]+"."+parts[1]+"."+Base64.encodeBase64URLSafeString(derSignature); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 1a8c6c2d..0460c287 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -176,7 +176,48 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { return new HMACAlgorithm("HS256", "HmacSHA256", secret); } - + + /** + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256K". + * + * @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance. + * @return a valid ECDSA256 Algorithm. + * @throws IllegalArgumentException if the Key Provider is null. + */ + public static Algorithm ECDSA256K(ECDSAKeyProvider keyProvider) throws IllegalArgumentException { + return new ECDSAAlgorithm("ES256K", "SHA256withECDSA", 32, keyProvider); + } + + /** + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256K". + * + * @param publicKey the key to use in the verify instance. + * @param privateKey the key to use in the signing instance. + * @return a valid ECDSA256 Algorithm. + * @throws IllegalArgumentException if the provided Key is null. + */ + public static Algorithm ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { + return ECDSA256K(ECDSAAlgorithm.providerForKeys(publicKey, privateKey)); + } + + /** + * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". + * + * @param key the key to use in the verify or signing instance. + * @return a valid ECDSA256 Algorithm. + * @throws IllegalArgumentException if the provided Key is null. + * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECDSAKeyProvider)} + */ + @Deprecated + public static Algorithm ECDSA256K(ECKey key) throws IllegalArgumentException { + ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; + ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; + return ECDSA256K(publicKey, privateKey); + } + + + + /** * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * @@ -198,6 +239,8 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { return new HMACAlgorithm("HS512", "HmacSHA512", secret); } + + /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index 41098c25..a7f18f29 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -25,6 +25,7 @@ public class ConcurrentVerifyTest { private static final int REPEAT_COUNT = 1000; private static final String PUBLIC_KEY_FILE = "src/test/resources/rsa-public.pem"; private static final String PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public.pem"; + private static final String PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public.pem"; private static final String PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public.pem"; private static final String PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public.pem"; @@ -84,7 +85,7 @@ public void shouldPassHMAC256Verification() throws Exception { concurrentVerify(verifier, token); } - + @Test public void shouldPassHMAC384Verification() throws Exception { String token = "eyJhbGciOiJIUzM4NCIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.uztpK_wUMYJhrRv8SV-1LU4aPnwl-EM1q-wJnqgyb5DHoDteP6lN_gE1xnZJH5vw"; @@ -139,7 +140,17 @@ public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { concurrentVerify(verifier, token); } - + + @Test + public void shouldPassECDSA256KVerificationWithJOSESignature() throws Exception { + String token = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJhdXRoMCJ9.W-AbsnuQ4vqmPftAyQuF09hn3oGn3tN7VGergxyMbK74yEzDV-mLyC3o3fxXrZxcW5h01DM6BckNag7ZcimPjw"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(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"; diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index c8dcae8d..1387425a 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -27,6 +27,7 @@ public class JWTCreatorTest { private static final String PRIVATE_KEY_FILE_RSA = "src/test/resources/rsa-private.pem"; private static final String PRIVATE_KEY_FILE_EC_256 = "src/test/resources/ec256-key-private.pem"; + private static final String PRIVATE_KEY_FILE_EC_256K = "src/test/resources/ec256k-key-private.pem"; @Rule public ExpectedException exception = ExpectedException.none(); @@ -157,7 +158,41 @@ public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception { String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } - + + @Test + public void shouldAddKeyIdIfAvailableFromECDSAKAlgorithms() throws Exception { + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K + , "EC"); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .sign(Algorithm.ECDSA256K(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); + } + + @Test + public void shouldNotOverwriteKeyIdIfAddedFromECDSAKAlgorithms() throws Exception { + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKeyId()).thenReturn("my-key-id"); + when(provider.getPrivateKey()).thenReturn(privateKey); + + String signed = JWTCreator.init() + .withKeyId("real-key-id") + .sign(Algorithm.ECDSA256(provider)); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); + } + @Test public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception { ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); @@ -683,4 +718,4 @@ public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception { .sign(Algorithm.HMAC256("secret")); } -} \ No newline at end of file +} diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 7cbe4b22..a51687e8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -26,9 +26,11 @@ public class JWTTest { private static final String PRIVATE_KEY_FILE_RSA = "src/test/resources/rsa-private.pem"; private static final String PUBLIC_KEY_FILE_EC_256 = "src/test/resources/ec256-key-public.pem"; + private static final String PUBLIC_KEY_FILE_EC_256K = "src/test/resources/ec256k-key-public.pem"; private static final String PUBLIC_KEY_FILE_EC_384 = "src/test/resources/ec384-key-public.pem"; private static final String PUBLIC_KEY_FILE_EC_512 = "src/test/resources/ec512-key-public.pem"; private static final String PRIVATE_KEY_FILE_EC_256 = "src/test/resources/ec256-key-private.pem"; + private static final String PRIVATE_KEY_FILE_EC_256K = "src/test/resources/ec256k-key-private.pem"; private static final String PRIVATE_KEY_FILE_EC_384 = "src/test/resources/ec384-key-private.pem"; private static final String PRIVATE_KEY_FILE_EC_512 = "src/test/resources/ec512-key-private.pem"; @@ -494,7 +496,23 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { .build(); assertThat(verified, is(notNullValue())); } - + + @Test + public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { + String signed = JWT.create().sign(Algorithm.ECDSA256K((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"))); + assertThat(signed, is(notNullValue())); + + String[] parts = signed.split("\\."); + String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256K")); + assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); + assertThat(parts[1], is("e30")); + + JWTVerifier verified = JWT.require(Algorithm.ECDSA256K((ECKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_256K, "EC"))) + .build(); + assertThat(verified, is(notNullValue())); + } + @Test public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { String signed = JWT.create().sign(Algorithm.ECDSA384((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_384, "EC"))); diff --git a/lib/src/test/java/com/auth0/jwt/PemUtils.java b/lib/src/test/java/com/auth0/jwt/PemUtils.java index 5f026b0a..6cd7703b 100644 --- a/lib/src/test/java/com/auth0/jwt/PemUtils.java +++ b/lib/src/test/java/com/auth0/jwt/PemUtils.java @@ -1,5 +1,6 @@ package com.auth0.jwt; +import org.bouncycastle.jce.provider.PEMUtil; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; @@ -11,10 +12,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; +import java.security.spec.*; public class PemUtils { @@ -43,7 +41,7 @@ private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) { return publicKey; } - + private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) { PrivateKey privateKey = null; try { diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index f4f437f9..8769a4f2 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -449,6 +449,51 @@ public void shouldCreateECDSA256AlgorithmWithProvider() throws Exception { assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); } + + @Test + public void shouldCreateECDSA256KAlgorithmWithPublicKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); + Algorithm algorithm = Algorithm.ECDSA256K(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256K")); + } + + @Test + public void shouldCreateECDSA256KAlgorithmWithPrivateKey() throws Exception { + ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); + Algorithm algorithm = Algorithm.ECDSA256K(key); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256K")); + } + + @Test + public void shouldCreateECDSA256KAlgorithmWithBothKeys() throws Exception { + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + Algorithm algorithm = Algorithm.ECDSA256K(publicKey, privateKey); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256K")); + } + + @Test + public void shouldCreateECDSA256KAlgorithmWithProvider() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + Algorithm algorithm = Algorithm.ECDSA256K(provider); + + assertThat(algorithm, is(notNullValue())); + assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); + assertThat(algorithm.getDescription(), is("SHA256withECDSA")); + assertThat(algorithm.getName(), is("ES256K")); + } @Test public void shouldCreateECDSA384AlgorithmWithPublicKey() throws Exception { 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 b50832d1..7f7b9c94 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -40,7 +40,12 @@ public class ECDSAAlgorithmTest { 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_256K = "src/test/resources/ec256k-key-private.pem"; + private static final String PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public.pem"; + private static final String INVALID_PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public-invalid.pem"; + private static final String ES256K_JWT = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.e30.2ggPsc4xwQhYcgJueo3uQ14MpaVJ3AbEE8UE-wA9fc8SMibeW54gjZbikL-JBHqhEwc22Cp8DNOtadXsM81RGQ"; + 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"; @@ -183,7 +188,112 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); } - + + @Test + public void shouldPassECDSA256KVerificationWithJOSESignature() throws Exception { + ECPublicKey key = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(key, null); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldThrowOnECDSA256KVerificationWithDERSignature() 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 = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.e30.MEUCIQDaCA-xzjHBCFhyAm56je5DXgylpUncBsQTxQT7AD19zwIgEjIm3lueII2W4pC_iQR6oRMHNtgqfAzTrWnV7DPNURk"; + + ECPublicKey key = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(key, null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA256KVerificationWithJOSESignatureWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC") + , (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldPassECDSA256KVerificationWithProvidedPublicKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + when(provider.getPublicKeyById("my-key-id")).thenReturn((ECPublicKey) publicKey); + Algorithm algorithm = Algorithm.ECDSA256K(provider); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWhenProvidedPublicKeyIsNull() 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); + Algorithm algorithm = Algorithm.ECDSA256K(provider); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWithInvalidPublicKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() 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."))); + Algorithm algorithm = Algorithm.ECDSA256K(null, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() 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 = ES256K_JWT.substring(0,ES256K_JWT.length()-1); + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() 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 = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidDERSignature() 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 = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } @Test public void shouldPassECDSA384VerificationWithJOSESignature() throws Exception { String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.50UU5VKNdF1wfykY8jQBKpvuHZoe6IZBJm5NvoB8bR-hnRg6ti-CHbmvoRtlLfnHfwITa_8cJMy6TenMC2g63GQHytc8rYoXqbwtS4R0Ko_AXbLFUmfxnGnMC6v4MS_z"; diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java index 452c0074..dbecae1e 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -32,6 +32,11 @@ import static org.mockito.Mockito.when; public class ECDSABouncyCastleProviderTests { + + + private static final String PRIVATE_KEY_FILE_256K = "src/test/resources/ec256k-key-private.pem"; + private static final String PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public.pem"; + private static final String INVALID_PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public-invalid.pem"; 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"; @@ -44,7 +49,9 @@ public class ECDSABouncyCastleProviderTests { 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"; - + + private static final String ES256K_JWT = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.e30.2ggPsc4xwQhYcgJueo3uQ14MpaVJ3AbEE8UE-wA9fc8SMibeW54gjZbikL-JBHqhEwc22Cp8DNOtadXsM81RGQ"; + @Rule public ExpectedException exception = ExpectedException.none(); private static final Provider bcProvider = new BouncyCastleProvider(); @@ -72,7 +79,112 @@ public void shouldPreferBouncyCastleProvider() throws Exception { } // Verify - + @Test + public void shouldPassECDSA256KVerificationWithJOSESignature() throws Exception { + ECPublicKey key = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(key, null); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldThrowOnECDSA256KVerificationWithDERSignature() 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 = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.e30.MEUCIQDaCA-xzjHBCFhyAm56je5DXgylpUncBsQTxQT7AD19zwIgEjIm3lueII2W4pC_iQR6oRMHNtgqfAzTrWnV7DPNURk"; + + ECPublicKey key = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(key, null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA256KVerificationWithJOSESignatureWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC") + , (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldPassECDSA256KVerificationWithProvidedPublicKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + when(provider.getPublicKeyById("my-key-id")).thenReturn((ECPublicKey) publicKey); + Algorithm algorithm = Algorithm.ECDSA256K(provider); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWhenProvidedPublicKeyIsNull() 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); + Algorithm algorithm = Algorithm.ECDSA256K(provider); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWithInvalidPublicKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() 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."))); + Algorithm algorithm = Algorithm.ECDSA256K(null, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); + algorithm.verify(JWT.decode(ES256K_JWT)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() 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 = ES256K_JWT.substring(0,ES256K_JWT.length()-1); + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() 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 = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256KVerificationOnInvalidDERSignature() 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 = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); + algorithm.verify(JWT.decode(jwt)); + } + @Test public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; diff --git a/lib/src/test/resources/ec256k-key-private.pem b/lib/src/test/resources/ec256k-key-private.pem new file mode 100644 index 00000000..43038c8e --- /dev/null +++ b/lib/src/test/resources/ec256k-key-private.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgN78SHuwC8BVLUbuEBXC9 +sn9LuX9a7yqIt1xaiPupqe6hRANCAAS7s8bnSsGqrv7YBqxqJiQKiyPJvcHTe2UG +7FpOaAlqIUWL5xbpCZbcH9yqozS55KMVxG78KskeuET7hl01J6EU +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/src/test/resources/ec256k-key-public-invalid.pem b/lib/src/test/resources/ec256k-key-public-invalid.pem new file mode 100644 index 00000000..8809e21b --- /dev/null +++ b/lib/src/test/resources/ec256k-key-public-invalid.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEXzvxXn69y0I54+Yr0pMXqGyCb1R8D4dS +xaYhaRuN2fsV+unbFWUYViRoYUgMAbmvnXzVmThLUeP/PTmlv9lm7Q== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/lib/src/test/resources/ec256k-key-public.pem b/lib/src/test/resources/ec256k-key-public.pem new file mode 100644 index 00000000..1513ed70 --- /dev/null +++ b/lib/src/test/resources/ec256k-key-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEu7PG50rBqq7+2AasaiYkCosjyb3B03tl +BuxaTmgJaiFFi+cW6QmW3B/cqqM0ueSjFcRu/CrJHrhE+4ZdNSehFA== +-----END PUBLIC KEY----- \ No newline at end of file From 10c443768cee70e693bb81c847c4a35af6a363f3 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 14 Sep 2020 17:35:39 -0500 Subject: [PATCH 155/355] Review feedback - remove deprecated method, whitespace fixes --- .../com/auth0/jwt/algorithms/Algorithm.java | 24 +++---------------- .../com/auth0/jwt/ConcurrentVerifyTest.java | 9 +++++-- lib/src/test/java/com/auth0/jwt/JWTTest.java | 9 +++++-- .../auth0/jwt/algorithms/AlgorithmTest.java | 24 +------------------ 4 files changed, 18 insertions(+), 48 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 0460c287..d6a3a2f4 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -176,7 +176,7 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { return new HMACAlgorithm("HS256", "HmacSHA256", secret); } - + /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256K". * @@ -187,7 +187,7 @@ public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { public static Algorithm ECDSA256K(ECDSAKeyProvider keyProvider) throws IllegalArgumentException { return new ECDSAAlgorithm("ES256K", "SHA256withECDSA", 32, keyProvider); } - + /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256K". * @@ -199,25 +199,7 @@ public static Algorithm ECDSA256K(ECDSAKeyProvider keyProvider) throws IllegalAr public static Algorithm ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException { return ECDSA256K(ECDSAAlgorithm.providerForKeys(publicKey, privateKey)); } - - /** - * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". - * - * @param key the key to use in the verify or signing instance. - * @return a valid ECDSA256 Algorithm. - * @throws IllegalArgumentException if the provided Key is null. - * @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECDSAKeyProvider)} - */ - @Deprecated - public static Algorithm ECDSA256K(ECKey key) throws IllegalArgumentException { - ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey) key : null; - ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey) key : null; - return ECDSA256K(publicKey, privateKey); - } - - - - + /** * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index a7f18f29..f4de1f71 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -10,11 +10,14 @@ import org.junit.rules.ExpectedException; import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.util.Collections; import java.util.List; import java.util.concurrent.*; +import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; //@Ignore("Skipping concurrency tests") @@ -26,6 +29,7 @@ public class ConcurrentVerifyTest { private static final String PUBLIC_KEY_FILE = "src/test/resources/rsa-public.pem"; private static final String PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public.pem"; private static final String PUBLIC_KEY_FILE_256K = "src/test/resources/ec256k-key-public.pem"; + private static final String PRIVATE_KEY_FILE_256K = "src/test/resources/ec256k-key-private.pem"; private static final String PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public.pem"; private static final String PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public.pem"; @@ -144,8 +148,9 @@ public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { @Test public void shouldPassECDSA256KVerificationWithJOSESignature() throws Exception { String token = "eyJraWQiOiJteS1rZXktaWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJhdXRoMCJ9.W-AbsnuQ4vqmPftAyQuF09hn3oGn3tN7VGergxyMbK74yEzDV-mLyC3o3fxXrZxcW5h01DM6BckNag7ZcimPjw"; - ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); - Algorithm algorithm = Algorithm.ECDSA256K(key); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC"); + Algorithm algorithm = Algorithm.ECDSA256K(publicKey, privateKey); JWTVerifier verifier = JWTVerifier.init(algorithm).withIssuer("auth0").build(); concurrentVerify(verifier, token); diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index a51687e8..79eb1ea8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -12,6 +12,8 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.util.Date; @@ -499,7 +501,10 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { @Test public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { - String signed = JWT.create().sign(Algorithm.ECDSA256K((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"))); + ECPublicKey publicKey = (ECPublicKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_256K, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"); + + String signed = JWT.create().sign(Algorithm.ECDSA256K(publicKey, privateKey) ); assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); @@ -508,7 +513,7 @@ public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); - JWTVerifier verified = JWT.require(Algorithm.ECDSA256K((ECKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_256K, "EC"))) + JWTVerifier verified = JWT.require(Algorithm.ECDSA256K(publicKey, privateKey)) .build(); assertThat(verified, is(notNullValue())); } diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java index 8769a4f2..50430017 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/AlgorithmTest.java @@ -449,29 +449,7 @@ public void shouldCreateECDSA256AlgorithmWithProvider() throws Exception { assertThat(algorithm.getDescription(), is("SHA256withECDSA")); assertThat(algorithm.getName(), is("ES256")); } - - @Test - public void shouldCreateECDSA256KAlgorithmWithPublicKey() throws Exception { - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPublicKey.class)); - Algorithm algorithm = Algorithm.ECDSA256K(key); - - assertThat(algorithm, is(notNullValue())); - assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); - assertThat(algorithm.getDescription(), is("SHA256withECDSA")); - assertThat(algorithm.getName(), is("ES256K")); - } - - @Test - public void shouldCreateECDSA256KAlgorithmWithPrivateKey() throws Exception { - ECKey key = mock(ECKey.class, withSettings().extraInterfaces(ECPrivateKey.class)); - Algorithm algorithm = Algorithm.ECDSA256K(key); - - assertThat(algorithm, is(notNullValue())); - assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class))); - assertThat(algorithm.getDescription(), is("SHA256withECDSA")); - assertThat(algorithm.getName(), is("ES256K")); - } - + @Test public void shouldCreateECDSA256KAlgorithmWithBothKeys() throws Exception { ECPublicKey publicKey = mock(ECPublicKey.class); From 938f1a5e973eb2cc43902150888bc4cba7d4f808 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 14 Sep 2020 17:36:17 -0500 Subject: [PATCH 156/355] Remove README-ES256K.txt file --- lib/README-ES256K.txt | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 lib/README-ES256K.txt diff --git a/lib/README-ES256K.txt b/lib/README-ES256K.txt deleted file mode 100644 index 02fdf82e..00000000 --- a/lib/README-ES256K.txt +++ /dev/null @@ -1,21 +0,0 @@ - -How to create a secp256k1 key to be used with ES256K algorithm: - -## first we generate the key -openssl ecparam -genkey -name secp256k1 -out KEY.pem -text -## the file is split into 2 parts a curve specification (EC PARAMETERS) and the key value -## this format is not suitable to be read by the PermUtils PEM reader so we have to convert it -## into PCKS8 format: - -openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in KEY.pem -out ec256k-key.private.pem - -## the public key is then calculated as follows: -openssl ec -pubout -in KEY.pem -out ec256k-key-public.pem - -## -## how to get DER signature -ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256K, "EC")); -String[] parts = ES256K_JWT.split("\\."); - -byte[] derSignature = algorithm256.JOSEToDER(Base64.decodeBase64(parts[2])); -String jwt=parts[0]+"."+parts[1]+"."+Base64.encodeBase64URLSafeString(derSignature); From 4a55c3066ad0f5c4c5b14d55ea58a19c4bf8b4a4 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 16 Sep 2020 09:18:46 -0500 Subject: [PATCH 157/355] review feedback - whitespace and optimized imports --- .../test/java/com/auth0/jwt/JWTCreatorTest.java | 17 ++++++++--------- lib/src/test/java/com/auth0/jwt/JWTTest.java | 14 +++++++------- lib/src/test/java/com/auth0/jwt/PemUtils.java | 6 ++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 1387425a..2d9179ff 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -158,41 +158,40 @@ public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception { String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } - + @Test public void shouldAddKeyIdIfAvailableFromECDSAKAlgorithms() throws Exception { - ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K - , "EC"); + ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"); ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); - + String signed = JWTCreator.init() .sign(Algorithm.ECDSA256K(provider)); - + assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } - + @Test public void shouldNotOverwriteKeyIdIfAddedFromECDSAKAlgorithms() throws Exception { ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"); ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); when(provider.getPrivateKeyId()).thenReturn("my-key-id"); when(provider.getPrivateKey()).thenReturn(privateKey); - + String signed = JWTCreator.init() .withKeyId("real-key-id") .sign(Algorithm.ECDSA256(provider)); - + assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } - + @Test public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception { ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC"); diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 79eb1ea8..b778be13 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -86,8 +86,8 @@ public void shouldVerifyDecodedToken() throws Exception { DecodedJWT decodedJWT = JWT.decode(token); RSAKey key = (RSAKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_RSA, "RSA"); DecodedJWT jwt = JWT.require(Algorithm.RSA512(key)) - .build() - .verify(decodedJWT); + .build() + .verify(decodedJWT); assertThat(jwt, is(notNullValue())); } @@ -498,26 +498,26 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { .build(); assertThat(verified, is(notNullValue())); } - + @Test public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { ECPublicKey publicKey = (ECPublicKey) PemUtils.readPublicKeyFromFile(PUBLIC_KEY_FILE_EC_256K, "EC"); ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256K, "EC"); - String signed = JWT.create().sign(Algorithm.ECDSA256K(publicKey, privateKey) ); + String signed = JWT.create().sign(Algorithm.ECDSA256K(publicKey, privateKey)); assertThat(signed, is(notNullValue())); - + String[] parts = signed.split("\\."); String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256K")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); - + JWTVerifier verified = JWT.require(Algorithm.ECDSA256K(publicKey, privateKey)) .build(); assertThat(verified, is(notNullValue())); } - + @Test public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { String signed = JWT.create().sign(Algorithm.ECDSA384((ECKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_384, "EC"))); diff --git a/lib/src/test/java/com/auth0/jwt/PemUtils.java b/lib/src/test/java/com/auth0/jwt/PemUtils.java index 6cd7703b..6c92e05e 100644 --- a/lib/src/test/java/com/auth0/jwt/PemUtils.java +++ b/lib/src/test/java/com/auth0/jwt/PemUtils.java @@ -1,6 +1,5 @@ package com.auth0.jwt; -import org.bouncycastle.jce.provider.PEMUtil; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; @@ -12,7 +11,10 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; public class PemUtils { From 006c073e87d83a8da492882b7a16d7299e1cc4c7 Mon Sep 17 00:00:00 2001 From: Javier Salinas Date: Thu, 17 Sep 2020 18:25:50 +0200 Subject: [PATCH 158/355] Add gh-action to validate gradle wrapper --- .github/workflows/gradle-wrapper-validation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 00000000..a015578a --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "validation/gradlew" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 From ac92da2659cfeea766aa404aa7d8d38ff26a400f Mon Sep 17 00:00:00 2001 From: Javier Salinas Date: Thu, 17 Sep 2020 18:25:56 +0200 Subject: [PATCH 159/355] Upgrade Gradle to 6.6.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 35 ++++++++++------------- gradlew.bat | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 22808 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 delta 20005 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 nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2 Date: Thu, 24 Sep 2020 15:17:58 -0500 Subject: [PATCH 160/355] Add ability to verify claim presence (#442) * add ability to verify claim presence only * remove commented-out method * Use singleton class to mark a claim should have only presence checked * minor refactoring to claim verification logic for readability * add test for null claim name --- .../main/java/com/auth0/jwt/JWTVerifier.java | 88 +++++++---- .../auth0/jwt/interfaces/Verification.java | 8 + .../java/com/auth0/jwt/JWTVerifierTest.java | 141 ++++++++++++++++++ 3 files changed, 212 insertions(+), 25 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 487addaf..a311ab33 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; +import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; @@ -115,6 +116,13 @@ public Verification withJWTId(String jwtId) { return this; } + @Override + public Verification withClaimPresence(String name) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, NonEmptyClaim.getInstance()); + return this; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -289,35 +297,49 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; + if (entry.getValue() instanceof NonEmptyClaim) { + assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); + } else { + verifyClaimValues(jwt, entry); } } } + private void verifyClaimValues(DecodedJWT jwt, Map.Entry entry) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + + private void assertClaimPresent(Claim claim, String claimName) { + if (claim instanceof NullClaim) { + throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName)); + } + } + private void assertValidClaim(Claim claim, String claimName, Object value) { boolean isValid = false; if (value instanceof String) { @@ -400,4 +422,20 @@ private void assertValidIssuerClaim(String issuer, List value) { throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); } } + + /** + * Simple singleton used to mark that a claim should only be verified for presence. + */ + private static class NonEmptyClaim { + private static NonEmptyClaim nonEmptyClaim; + + private NonEmptyClaim() {} + + public static NonEmptyClaim getInstance() { + if (nonEmptyClaim == null) { + nonEmptyClaim = new NonEmptyClaim(); + } + return nonEmptyClaim; + } + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 72ae35d2..e23f4340 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -80,6 +80,14 @@ public interface Verification { */ Verification withJWTId(String jwtId); + /** + * Require a claim to be present, with any value. + * @param name the Claim's name. + * @return this same Verification instance + * @throws IllegalArgumentException if the name is null. + */ + Verification withClaimPresence(String name) throws IllegalArgumentException; + /** * Require a specific Claim value. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d57ca187..156a39a6 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -790,4 +790,145 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() throws Exception { assertThat(jwt, is(notNullValue())); } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'missing' is not present in the JWT."); + + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("missing") + .build(); + + verifier.verify(jwt); + } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceWhenClaimNameIsNull() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Custom Claim's name can't be null."); + + String jwt = JWTCreator.init() + .withClaim("custom", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence(null); + } + + @Test + public void shouldVerifyStringClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyBooleanClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", true) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyIntegerClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 123) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyLongClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 922337203685477600L) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyDoubleClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 12.34) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyListClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonList("item")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyMapClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonMap("key", "value")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyStandardClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("aud", "any value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("aud") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } } From c5b785aca15c5c3265314f4f4d5fa7263594c205 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 25 Sep 2020 13:54:23 -0500 Subject: [PATCH 161/355] Release 3.11.0 --- CHANGELOG.md | 11 +++++++++++ README.md | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21095ff7..39da33e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## [3.11.0](https://github.com/auth0/java-jwt/tree/3.11.0) (2020-09-25) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.3...3.11.0) + +**Added** +- Add ability to verify claim presence [\#442](https://github.com/auth0/java-jwt/pull/442) ([jimmyjames](https://github.com/jimmyjames)) +- Add Support for secp256k1 algorithms (AKA ES256K) [\#439](https://github.com/auth0/java-jwt/pull/439) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Fix and document thread-safety [\#427](https://github.com/auth0/java-jwt/pull/427) ([lbalmaceda](https://github.com/lbalmaceda)) +- Wrap IllegalArgumentException into JWTDecodeException [\#426](https://github.com/auth0/java-jwt/pull/426) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.10.3](https://github.com/auth0/java-jwt/tree/3.10.3) (2020-04-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.2...3.10.3) diff --git a/README.md b/README.md index 1cdbe422..d1b5948c 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.10.3 + 3.11.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.10.3' +implementation 'com.auth0:java-jwt:3.11.0' ``` ## Available Algorithms From dd6bc25d9a7c2857bae29757a9198b798ac5734f Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 27 Oct 2020 08:54:20 -0300 Subject: [PATCH 162/355] Setup the CODEOWNERS for pull request reviews (#448) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c9ff4921..60f116c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdks-approver +* @auth0/dx-sdks-engineer From a26284a3228e867e47dc96c2a31b92ee8dda0d83 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 28 Oct 2020 13:49:09 -0500 Subject: [PATCH 163/355] update issue templates --- .github/ISSUE_TEMPLATE.md | 38 ---------------- .github/ISSUE_TEMPLATE/config.yml | 8 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 39 ++++++++++++++++ .github/ISSUE_TEMPLATE/report-a-bug.md | 55 +++++++++++++++++++++++ 4 files changed, 102 insertions(+), 38 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/report-a-bug.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 009ccd24..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ -In order to efficiently and accurately address your issue or feature request, please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. Please delete any sections or questions below that do not pertain to this request. - -For general support or usage questions, please use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com/). - -### Description - -Description of the bug or feature request and why it's a problem. Consider including: - -- The use case or overall problem you're trying to solve -- Information about when the problem started - -### Prerequisites - -- [ ] I have checked the documentation for this library in the README. -- [ ] I have checked the [Auth0 Community](https://community.auth0.com/) for related posts. -- [ ] I have checked for related or duplicate [Issues](https://github.com/auth0/java-jwt/issues) and [PRs](https://github.com/auth0/java-jwt/pulls). -- [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). -- [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). -- [ ] I am reporting this to the correct repository (note this library is used by many libraries, such as [auth0-java](https://github.com/auth0/auth0-java)). - -### Environment - -Please provide the following: - -- Version of this library used: -- Version of Java framework used: -- Additional libraries that might be affecting your instance: - -### Reproduction - -Detail the steps taken to reproduce this error and note if this issue can be reproduced consistently or if it is intermittent. - -Please include: - -- Code sample to reproduce the issue -- Log files (redact/remove sensitive information) -- Application settings (redact/remove sensitive information) -- Screenshots, if helpful diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..77c79de9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Auth0 Community + url: https://community.auth0.com/c/sdks + about: Discuss this SDK in the Auth0 Community forums + - name: Library Documentation + url: https://github.com/auth0/java-jwt/blob/master/README.md + about: Read the library documentation diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..5e9e90bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,39 @@ +--- +name: Feature request +about: Suggest an idea or a feature for this project +title: '' +labels: feature request +assignees: '' +--- + + + +### Describe the problem you'd like to have solved + + + +### Describe the ideal solution + + + +## Alternatives and current work-arounds + + + +### Additional information, if any + + diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md new file mode 100644 index 00000000..60505a3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -0,0 +1,55 @@ +--- +name: Report a bug +about: Have you found a bug or issue? Create a bug report for this SDK +title: '' +labels: bug report +assignees: '' +--- + + + +### Describe the problem + + + +### What was the expected behavior? + + + +### Reproduction + + +- Step 1.. +- Step 2.. +- ... + +### Environment + + + +- **Version of this library used:** +- **Version of Java used:** +- **Other modules/plugins/libraries that might be involved:** +- **Any other relevant information you think would be useful:** From 0dba806b221187b064e72c9f4a204c0140aecfcc Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Fri, 30 Oct 2020 09:35:40 -0300 Subject: [PATCH 164/355] Revert "Add license scan report and status" This reverts commit 984eaaf0fb74cba344d3f743aa0fa0d0724edcec. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d1b5948c..a6cab21c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ + + # Java JWT [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) [![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_shield) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -433,6 +434,3 @@ If you have found a bug or if you have a feature request, please report them at ## License This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. - - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fauth0%2Fjava-jwt?ref=badge_large) \ No newline at end of file From 2ba767459fd01f870fbf3375550f3a2c1147dc76 Mon Sep 17 00:00:00 2001 From: TeeVenDick Date: Tue, 3 Nov 2020 16:55:28 +0800 Subject: [PATCH 165/355] Fix broken docs links in README.md (#453) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6cab21c..eefe1ccd 100644 --- a/README.md +++ b/README.md @@ -411,12 +411,12 @@ If the values can't be converted to the given **Class Type** a `JWTDecodeExcepti Auth0 helps you to: -* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. -* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**. -* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user. -* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely. +* Add authentication with [multiple authentication sources](https://auth0.com/docs/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. +* Add authentication through more traditional **[username/password databases](https://auth0.com/docs/connections/database)**. +* Add support for **[linking different user accounts](https://auth0.com/docs/users/user-account-linking)** with the same user. +* Support for generating signed [Json Web Tokens](https://auth0.com/docs/tokens/json-web-tokens) to call your APIs and **flow the user identity** securely. * Analytics of how, when and where users are logging in. -* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules). +* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://auth0.com/docs/rules). ## Create a free account in Auth0 From 4471c5960e8a0f94e2d34181b4654d8f9bbfe681 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 18 Nov 2020 11:06:21 -0300 Subject: [PATCH 166/355] Setup pull-request and issue templates (#458) * Setup pull-request and issue templates * Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .github/ISSUE_TEMPLATE/report_a_bug.md | 55 +++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/report_a_bug.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 77c79de9..3cd7aa53 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Auth0 Community - url: https://community.auth0.com/c/sdks + url: https://community.auth0.com/c/sdks/5 about: Discuss this SDK in the Auth0 Community forums - name: Library Documentation url: https://github.com/auth0/java-jwt/blob/master/README.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5e9e90bf..68352ba2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -17,7 +17,7 @@ By submitting an Issue to this repository, you agree to the terms within the Aut ### Describe the problem you'd like to have solved ### Describe the ideal solution @@ -36,4 +36,4 @@ By submitting an Issue to this repository, you agree to the terms within the Aut +--> \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/report_a_bug.md b/.github/ISSUE_TEMPLATE/report_a_bug.md new file mode 100644 index 00000000..50b9fa7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report_a_bug.md @@ -0,0 +1,55 @@ +--- +name: Report a bug +about: Have you found a bug or issue? Create a bug report for this SDK +title: '' +labels: bug report +assignees: '' +--- + + + +### Describe the problem + + + +### What was the expected behavior? + + + +### Reproduction + + +- Step 1.. +- Step 2.. +- ... + +### Environment + + + +- **Version of this library used:** +- **Which framework are you using, if applicable:** +- **Other modules/plugins/libraries that might be involved:** +- **Any other relevant information you think would be useful:** \ No newline at end of file From 45775cb8071ce2d1f48491c510991fdff27d9829 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 18 Nov 2020 17:21:42 -0600 Subject: [PATCH 167/355] Add notice to README about Java 8 upcoming requirement --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eefe1ccd..712cb07d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. +> This library currently supports Java 7. Beginning soon, it will require Java 8 as the minimum supported Java version. See [this issue](https://github.com/auth0/java-jwt/issues/457) for additional information and timelines. + ## Installation The library is available on both Maven Central and Bintray, and the Javadoc is published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). From a787e2d79f890429fd7b7dc802f3f874db266bc2 Mon Sep 17 00:00:00 2001 From: LeeHainie <30332409+LeeHainie@users.noreply.github.com> Date: Mon, 23 Nov 2020 17:22:49 +0800 Subject: [PATCH 168/355] Thread-safe classes should be Shared statically Thread-safe classes should be Shared statically,This improves performance and reduces class creation costs. --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index bd0dff9d..340528ad 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -30,15 +30,21 @@ public final class JWTCreator { private final Algorithm algorithm; private final String headerJson; private final String payloadJson; + + private static final ObjectMapper mapper; + private static final SimpleModule module; + + static { + mapper = new ObjectMapper(); + module = new SimpleModule(); + module.addSerializer(ClaimsHolder.class, new PayloadSerializer()); + mapper.registerModule(module); + mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + } private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) throws JWTCreationException { this.algorithm = algorithm; try { - ObjectMapper mapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addSerializer(ClaimsHolder.class, new PayloadSerializer()); - mapper.registerModule(module); - mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); headerJson = mapper.writeValueAsString(headerClaims); payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims)); } catch (JsonProcessingException e) { From 0f9ee305334c6513e58014b113871a531d5738c9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Dec 2020 18:10:11 +0100 Subject: [PATCH 169/355] Update jackson-databind to 2.10.5.1 (fixes CVE-2020-25649) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index dcd0d615..9ea22404 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,7 +34,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From f616badb0a30963822d3fee8a789c133c73db547 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 15 Dec 2020 21:30:11 +0100 Subject: [PATCH 170/355] Target Java 8 (#455) * update gradle wrapper to 6.7 * change required java version to 8 * Update to Gradle 6.7.1 Co-authored-by: Jim Anderson --- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 ++ gradlew.bat | 25 +++++++---------------- lib/build.gradle | 17 ++++++++++----- settings.gradle | 11 ++++++++++ 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 12842 zcmY+q1ymhDmoSOmQr324hm`&0SZbS3R18r37J8Z0F-T>@gIK` zatajf2b1lO#&E_RnGDF51uk%8Wcxm3`%Yhe7Psx?*?eZ?8M9_+G(?L=s^OG1n#S(NP3gF?2Mr2^f5E7sM~iVC`Rn;(-^MZ zZu*ZXB;XmgvPls(e#)MMTObsEx9oNz-K?AmQ8pP&P7vqx*=5zxjU+ye_1R<%KSg1? z7H&Yh))(Ke!Pa+aVuWxPKa_~Qo_IH}*;tV8n~O*Xa?t3P^9=L%=wOL1=~{LVv}mU8Q#e6s>v}iV8cDP|EdY)`dp≶7^21 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

XiM^r_M1uFTw)(UPqqUJ zed7DJQvOpplYJoNl`A3a2Zr~v)Y}K|*%d7?8;dC*Af}0=(EXrk7hP8PM4v)ZawEv0 z5j5q_6vilv4*ppZ3Wke7%8b`odJu26#YseA?$sP8?@d?TpACTX`G^?%K=Hly9Y$Tj z5`i!}-WH0lQ3yt-&Pf&Z)OxjKfAZ3X`YI2)5o@9EiOA}?kX`^rk;yaOh^Li4VHcT& zd6ztJz^`H!8>cL)a%?Lnofzo0$WPrgkNcP#u@{%~EvA7g#go z+Q4$Q^o_MJTE0G_&9@cgp)WF>2zo>AG}C|(~E^_?ijc2EQ>> zFH1Lxc@i!gDHxvEsrfc+Kiz3Q6Z*NM+U6DH6*-Fv{YDY4>U&eegGH~Xmk~!sd6fw2 z!6^4(MZWhzf(xs6BHy=oS+dir0_JW(j*AIu$9&&p@}T;&6s7(yYidEdkp6pk9}Z(F zk6lG`d!HcJWP{7X)&P5FW;XWU6;zke2e+g*#1kW4L0Auj5pVA45Bhzt{8lj)XyMHu z0p;Q}t*NLnXbGQOTC9WdOQsX;_yLThOP$mzcEbqSBifmDecV;Fqhtm#KxfJTMhY!K zw{1L9za2QtuIWXu0ZYm@Uy72(9^vYajuP$(VAKn+&Jp z!%S;#BqO;02)n~6pFYK8oRC6wXx@kyb?VENM7l?NFg)%^=q4aZ* zwVCZxA8lalF&n`vk#gxD@!9~Aktc+h2UUUax3q2XKQLuL@CpVEpx2IyiH3bALY+Oi zDydtacE1Z|$z5qsCG=%F{}8_ozwieWuM4VcDesuu+wX&YjHm^Ryx)pVtiSLpch7<` zIw9QM%I;(VG9~#Rw2JNt+;?_=O2lA#YSudpgsM`e&(}1 zGdj7U)St4`+Cj#Vn>a;Bj~Q3S1G54;gzq)`b_Hh~ip(`BK#GMkI*RUcK+6ycjl?QN zP%sH5*R$$6A&8j8O4iFJhuHMXLJm|drH+=!_{`60&+tC8*m3Z=YsR@~%eaZyWQI|W zie7;BRL6`LR+4B{%FY~;-^*1GGTCLp!U6VoS36GUAWv$Rcc#|a7DD01{QPB=VyT)? z+1ZV>P=bP1;>v-+j0;BwenO`EKcIbmB3$r*@a~=?5#KB_R$3bTg zOjEutZmWuylIJQEM?28D!4uA#0CZEJHar0TFAW|NwP;WL|EIb_L2>;}e*N!K9E61S zH&u@m!n#CH{C_j}{#ybCRU8z7`Cs{b>@bxSkp3lOm`qAYgB>npnw%V>w}t_+S_Z)s zFhGKq^e%m@=^@sD0CJdAK=`Upbr}?}N zf;snqpt#dOwhxFg82(%Tw=ND+@`O0JGeOWd7-3R8A%Yu%FhiaYXD>p?t2+o%_1CKE z{g(>=g%}X(O%M!}KZK%$7-F<34wD-24`y$WLDv6z?ty=_b)Oi*x&?v}3j0f`AV3HL zWPAYw!XNs-1EmZ9=d=$6LAJISVJ4&gQM5=bh{!f0OmFNz8oMnGgOl_RK5VPP3@87C zpLS$muCo5YTmOslSjFLb`AXIJ@1|O{pm5r z9MxUb-8HMe*|TpFa%dE?+7}MVQ-A-N8wvtq85ROU2%KYt4etC52X0%W08haQgGSU| zvw=K$djTYSLy@4My_Te_8JcZp8Oozg{-ey>*Nm7BkGrX(M~HU6N16U>DSj<`;cy`u zR?0B23zx|*o1V=#fLZ=wd6*NfWjC`pqA^mt>DTZjGBeX`0ZK>Qgxz+37Dyb#NT4WT zl@7Lmh{WdY*h%e_bo6(oXKw=`(9=mft10fOR4< zCOUungtJZPAM~qw4Ydy9PhvYTi1ZRqR5nY)?OX2O3FJ6VXy&`&H%^U|d>N>@f zQ}Cd;DV}_bNiVTW8HcUJDRvXCUwGx|(r2-KbXY>jpd!~jmt(

p*eWM!w;MI3hb2}DlKZV3$r+FsPh`1u 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/lib/build.gradle b/lib/build.gradle index 9ea22404..eebfe917 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,7 +1,8 @@ plugins { - id "com.jfrog.bintray" version "1.8.4" - id "com.auth0.gradle.oss-library.java" version "0.11.0" - id "jacoco" + id 'java' + id 'jacoco' + id 'com.jfrog.bintray' + id 'com.auth0.gradle.oss-library.java' } logger.lifecycle("Using version ${version} for ${group}.${name}") @@ -28,9 +29,15 @@ oss { } } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + compileJava { - sourceCompatibility '1.7' - targetCompatibility '1.7' + sourceCompatibility '1.8' + targetCompatibility '1.8' } dependencies { diff --git a/settings.gradle b/settings.gradle index ce7f0a64..d3c7fb14 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,13 @@ +pluginManagement { + repositories { + gradlePluginPortal() + jcenter() + } + plugins { + id 'com.jfrog.bintray' version '1.8.5' + id 'com.auth0.gradle.oss-library.java' version '0.12.1' + } +} + include ':java-jwt' project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') \ No newline at end of file From 58439f41bed7ead6911e47cf71dfb3de59d5cb12 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 18 Dec 2020 08:27:03 -0600 Subject: [PATCH 171/355] Release 3.12.0 --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da33e9..12ab335a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [3.12.0](https://github.com/auth0/java-jwt/tree/3.12.0) (2020-12-18) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.11.0...3.12.0) + +**Changed** +- Thread-safe classes should be Shared statically [\#462](https://github.com/auth0/java-jwt/pull/462) ([LeeHainie](https://github.com/LeeHainie)) + +**Security** +- Update jackson-databind to 2.10.5.1 (fixes CVE-2020-25649) [\#463](https://github.com/auth0/java-jwt/pull/463) ([overheadhunter](https://github.com/overheadhunter)) + +**Breaking changes** +- Target Java 8 [\#455](https://github.com/auth0/java-jwt/pull/455) ([lbalmaceda](https://github.com/lbalmaceda)) + ## [3.11.0](https://github.com/auth0/java-jwt/tree/3.11.0) (2020-09-25) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.10.3...3.11.0) diff --git a/README.md b/README.md index 712cb07d..618fa12e 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.11.0 + 3.12.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.11.0' +implementation 'com.auth0:java-jwt:3.12.0' ``` ## Available Algorithms From 63812af383245d526c0f89ec02abf861967af031 Mon Sep 17 00:00:00 2001 From: darveshsingh <56809416+darveshsingh@users.noreply.github.com> Date: Wed, 16 Dec 2020 19:09:15 -0500 Subject: [PATCH 172/355] Fix: updated jackson-databind to 2.10.0.pr3 to block CVE-2020-25649 ## Changes Version bump of jackson-databind to 2.11.0 ## References: [CVE-2020-25649](https://bugzilla.redhat.com/show_bug.cgi?id=1887664) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index eebfe917..fd920e2c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -41,7 +41,7 @@ compileJava { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' From 3b87927a8e449d867a1312086164afd08c56bda7 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 20 Jan 2021 13:00:52 -0600 Subject: [PATCH 173/355] Release 3.12.1 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12ab335a..fd2e477e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.12.1](https://github.com/auth0/java-jwt/tree/3.12.1) (2021-01-20) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.0...3.12.1) + +**Changed** +- Update jackson-databind to 2.11.0 [\#464](https://github.com/auth0/java-jwt/pull/464) ([darveshsingh](https://github.com/darveshsingh)) + ## [3.12.0](https://github.com/auth0/java-jwt/tree/3.12.0) (2020-12-18) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.11.0...3.12.0) diff --git a/README.md b/README.md index 618fa12e..90c71bbf 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.12.0 + 3.12.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.12.0' +implementation 'com.auth0:java-jwt:3.12.1' ``` ## Available Algorithms From 9e951cd9fd63a98b0a9eb5a0367f4cf840952306 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Thu, 28 Jan 2021 15:33:46 -0600 Subject: [PATCH 174/355] Add toString to Claim objects --- lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java | 5 +++++ lib/src/main/java/com/auth0/jwt/impl/NullClaim.java | 5 +++++ .../test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java | 7 +++++++ lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java | 4 ++++ 4 files changed, 21 insertions(+) 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 4e2aef63..ae266798 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -128,6 +128,11 @@ public boolean isNull() { return false; } + @Override + public String toString() { + return data.toString(); + } + /** * Helper method to extract a Claim from the given JsonNode tree. * diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 93bedbb1..8d10ca10 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -65,4 +65,9 @@ public Map asMap() throws JWTDecodeException { public T as(Class tClazz) throws JWTDecodeException { return null; } + + @Override + public String toString() { + return "Null Claim"; + } } 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 1a82fcec..84d0be4b 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -431,4 +431,11 @@ public void shouldReturnNonNullClaimWhenParsingBooleanValue() throws Exception { assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); } + + @Test + public void shouldDelegateToJsonNodeToString() { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + Claim claim = claimFromNode(value); + assertThat(claim.toString(), is(value.toString())); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java index d8ddd516..b15f3dad 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java @@ -70,4 +70,8 @@ public void shouldGetAsCustomClass() throws Exception { assertThat(claim.as(Object.class), is(nullValue())); } + @Test + public void shouldHaveToString() { + assertThat(claim.toString(), is("Null Claim")); + } } \ No newline at end of file From 7fe888ca1d3f9d5918f6247a17763d70c52b5607 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 1 Feb 2021 11:43:46 -0600 Subject: [PATCH 175/355] Update acceptedIssuedAt JavaDocs --- .../java/com/auth0/jwt/interfaces/Verification.java | 5 +++++ lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index e23f4340..f9904aac 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -64,6 +64,11 @@ public interface Verification { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. + * This method overrides the value set with {@link #acceptLeeway(long)}. + * If Issued At verification has been disabled with {@link #ignoreIssuedAt()}, no verification of the Issued At + * claim will be performed, and this method has no effect. + * + * Issued At Date is verified by default when the value is present, unless disabled * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Issued At Claim will still be valid. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 156a39a6..b8fd2f5e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -672,12 +672,14 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() throws Exception { @Test public void shouldOverrideAcceptIssuedAtWhenIgnoreIssuedAtFlagPassedAndSkipTheVerification() throws Exception { Clock clock = mock(Clock.class); - when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 1000)); + when(clock.getToday()).thenReturn(new Date(DATE_TOKEN_MS_VALUE - 10000)); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - DecodedJWT jwt = verification.acceptIssuedAt(20).ignoreIssuedAt() - .build() + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")) + .acceptIssuedAt(1) + .ignoreIssuedAt(); + DecodedJWT jwt = verification + .build(clock) .verify(token); assertThat(jwt, is(notNullValue())); From 3766cd8c4d0ed87b91f29ed314744f0db8e58f37 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Tue, 2 Feb 2021 12:23:20 -0600 Subject: [PATCH 176/355] Update JavaDoc to remove duplicated information, improve clarity --- .../main/java/com/auth0/jwt/interfaces/Verification.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index f9904aac..6821f3ae 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -65,11 +65,8 @@ public interface Verification { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. * This method overrides the value set with {@link #acceptLeeway(long)}. - * If Issued At verification has been disabled with {@link #ignoreIssuedAt()}, no verification of the Issued At - * claim will be performed, and this method has no effect. - * - * Issued At Date is verified by default when the value is present, unless disabled - * Issued At Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * By default, the Issued At claim is always verified when the value is present, unless disabled with {@link #ignoreIssuedAt()}. + * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, and this method has no effect. * * @param leeway the window in seconds in which the Issued At Claim will still be valid. * @return this same Verification instance. From 2ee2b3c8e131210b417e66ce7acdd88fdc06c0d6 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 1 Feb 2021 14:48:04 -0600 Subject: [PATCH 177/355] Add ability to verify an audience contains at least one --- .../main/java/com/auth0/jwt/JWTVerifier.java | 57 ++++++- .../auth0/jwt/interfaces/Verification.java | 24 ++- .../java/com/auth0/jwt/JWTVerifierTest.java | 158 +++++++++++++++++- 3 files changed, 232 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index a311ab33..2393f76c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -11,6 +11,7 @@ import com.auth0.jwt.interfaces.Verification; import java.util.*; +import java.util.stream.Collectors; /** * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also it's signature matches. @@ -23,12 +24,14 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { final Map claims; private final Clock clock; private final JWTParser parser; + private final AudienceVerificationStrategy audienceVerificationStrategy; - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + JWTVerifier(Algorithm algorithm, Map claims, Clock clock, AudienceVerificationStrategy audienceVerificationStrategy) { this.algorithm = algorithm; this.claims = Collections.unmodifiableMap(claims); this.clock = clock; this.parser = new JWTParser(); + this.audienceVerificationStrategy = audienceVerificationStrategy; } /** @@ -47,6 +50,7 @@ public static class BaseVerification implements Verification { private final Map claims; private long defaultLeeway; private boolean ignoreIssuedAt; + private AudienceVerificationStrategy audienceVerificationStrategy = AudienceVerificationStrategy.UNSET; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -72,6 +76,20 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { + if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { + throw new IllegalStateException("Audience validation behavior has already been configured."); + } + audienceVerificationStrategy = AudienceVerificationStrategy.EXACT; + requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + return this; + } + + @Override + public Verification withAnyOfAudience(String... audience) { + if (audienceVerificationStrategy == AudienceVerificationStrategy.EXACT) { + throw new IllegalStateException("Audience validation behavior has already been configured."); + } + audienceVerificationStrategy = AudienceVerificationStrategy.CONTAINS; requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @@ -200,7 +218,7 @@ public JWTVerifier build() { */ public JWTVerifier build(Clock clock) { addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); + return new JWTVerifier(algorithm, claims, clock, audienceVerificationStrategy); } private void assertPositive(long leeway) { @@ -412,8 +430,19 @@ private void assertDateIsPast(Date date, long leeway, Date today) { } private void assertValidAudienceClaim(List audience, List value) { - if (audience == null || !audience.containsAll(value)) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + String invalidMessage = "The Claim 'aud' value doesn't contain the required audience."; + if (audience == null) { + throw new InvalidClaimException(invalidMessage); + } + + if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { + if (Collections.disjoint(audience, value)) { + throw new InvalidClaimException(invalidMessage); + } + } else { + if (!audience.containsAll(value)) { + throw new InvalidClaimException(invalidMessage); + } } } @@ -438,4 +467,24 @@ public static NonEmptyClaim getInstance() { return nonEmptyClaim; } } + + /** + * Represents how the audience will be validated. + */ + enum AudienceVerificationStrategy { + /** + * No audience validation configured. + */ + UNSET, + + /** + * The JWT audience must match the expected audiences exactly. + */ + EXACT, + + /** + * The JWT audience must contain at least one of the expected audiences. + */ + CONTAINS + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 6821f3ae..2130fcd1 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -25,13 +25,35 @@ public interface Verification { Verification withSubject(String subject); /** - * Require a specific Audience ("aud") claim. + * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present + * in the "aud" claim. + * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been + * configured with {@link #withAnyOfAudience(String...)} * * @param audience the required Audience value * @return this same Verification instance. */ Verification withAudience(String... audience); + /** + * Require that the Audience ("aud") claim contain at least one of the specified audiences. + * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been + * configured with {@link #withAudience(String...)} + * + * @apiNote This method was added after the interface was released. + * It is defined as a default method for compatibility reasons. + * From version 4.0 on, the method will be abstract and all implementations of this interface + * will have to provide their own implementation. + * + * @implSpec The default implementation throws an {@linkplain UnsupportedOperationException}. + * + * @param audience the required Audience value for which the "aud" claim must contain at least one value. + * @return this same Verification instance. + */ + default Verification withAnyOfAudience(String... audience) { + throw new UnsupportedOperationException("withAnyOfAudience"); + } + /** * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. * Setting a specific leeway value on a given Claim will override this value for that Claim. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b8fd2f5e..1847824e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -111,6 +111,7 @@ public void shouldThrowOnInvalidSubject() throws Exception { @Test public void shouldValidateAudience() throws Exception { + // Token 'aud': ["Mark"] String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("Mark") @@ -128,6 +129,32 @@ public void shouldValidateAudience() throws Exception { assertThat(jwtArr, is(notNullValue())); } + @Test + public void shouldThrowIfConfiguringAllOfAfterAnyOfAudienceValidation() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Audience validation behavior has already been configured."); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark") + .withAudience("Mark") + .build() + .verify(token); + } + + @Test + public void shouldThrowIfConfiguringAnyOfAfterAllOfAudienceValidation() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Audience validation behavior has already been configured."); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark") + .withAnyOfAudience("Mark") + .build() + .verify(token); + } + @Test public void shouldAcceptPartialAudience() throws Exception { //Token 'aud' = ["Mark", "David", "John"] @@ -141,9 +168,70 @@ public void shouldAcceptPartialAudience() throws Exception { } @Test - public void shouldThrowOnInvalidAudience() throws Exception { + public void shouldAcceptOneOfAudience() { + // Token 'aud': ["Mark"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark") + .build() + .verify(token); + + assertThat(jwt, is(notNullValue())); + + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("John", "Jim") + .build() + .verify(tokenArr); + + assertThat(jwtArr, is(notNullValue())); + } + + @Test + public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Mark", "David", "John") + .build() + .verify(tokenArr); + + assertThat(jwtArr, is(notNullValue())); + } + + @Test + public void shouldThrowWhenAudienceHasNoneOfAcceptOneOfAudience() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Joe", "Jim") + .build() + .verify(tokenArr); + } + + @Test + public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark", "Joe") + .build() + .verify(token); + } + + @Test + public void shouldThrowWhenAudienceClaimIsNull() throws Exception { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud': null String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("nope") @@ -151,6 +239,19 @@ public void shouldThrowOnInvalidAudience() throws Exception { .verify(token); } + @Test + public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() throws Exception { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); + + // Token 'aud': null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("nope") + .build() + .verify(token); + } + @Test public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { Algorithm algorithm = mock(Algorithm.class); @@ -184,6 +285,39 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); } + @Test + public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience() + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + String emptyAud = " "; + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience(emptyAud) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + } + @Test public void shouldRemoveAudienceWhenPassingNull() throws Exception { Algorithm algorithm = mock(Algorithm.class); @@ -204,6 +338,26 @@ public void shouldRemoveAudienceWhenPassingNull() throws Exception { assertThat(verifier.claims, not(hasKey("aud"))); } + @Test + public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() throws Exception { + Algorithm algorithm = mock(Algorithm.class); + JWTVerifier verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience("John") + .withAnyOfAudience((String) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + + verifier = JWTVerifier.init(algorithm) + .withAnyOfAudience("John") + .withAnyOfAudience((String[]) null) + .build(); + + assertThat(verifier.claims, is(notNullValue())); + assertThat(verifier.claims, not(hasKey("aud"))); + } + @Test public void shouldThrowOnNullCustomClaimName() throws Exception { exception.expect(IllegalArgumentException.class); @@ -297,7 +451,7 @@ public void shouldThrowOnInvalidCustomClaimValue() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; Map map = new HashMap<>(); map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl()); + JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl(), JWTVerifier.AudienceVerificationStrategy.EXACT); verifier.verify(token); } From cbb00491d4b05a5f57d0d5f9c83209a9a9f09f64 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 3 Feb 2021 16:35:58 -0600 Subject: [PATCH 178/355] Manage audience validation strategy with internal map, allow strategies to be overridden --- .../main/java/com/auth0/jwt/JWTVerifier.java | 89 ++++++----------- .../auth0/jwt/interfaces/Verification.java | 10 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 99 +++++++++---------- 3 files changed, 84 insertions(+), 114 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 2393f76c..16a7c89f 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -24,14 +24,15 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { final Map claims; private final Clock clock; private final JWTParser parser; - private final AudienceVerificationStrategy audienceVerificationStrategy; - JWTVerifier(Algorithm algorithm, Map claims, Clock clock, AudienceVerificationStrategy audienceVerificationStrategy) { + static final String AUDIENCE_EXACT = "AUDIENCE_EXACT"; + static final String AUDIENCE_CONTAINS = "AUDIENCE_CONTAINS"; + + JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { this.algorithm = algorithm; this.claims = Collections.unmodifiableMap(claims); this.clock = clock; this.parser = new JWTParser(); - this.audienceVerificationStrategy = audienceVerificationStrategy; } /** @@ -50,7 +51,6 @@ public static class BaseVerification implements Verification { private final Map claims; private long defaultLeeway; private boolean ignoreIssuedAt; - private AudienceVerificationStrategy audienceVerificationStrategy = AudienceVerificationStrategy.UNSET; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -76,21 +76,15 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { - if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { - throw new IllegalStateException("Audience validation behavior has already been configured."); - } - audienceVerificationStrategy = AudienceVerificationStrategy.EXACT; - requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + claims.remove(AUDIENCE_CONTAINS); + requireClaim(AUDIENCE_EXACT, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @Override public Verification withAnyOfAudience(String... audience) { - if (audienceVerificationStrategy == AudienceVerificationStrategy.EXACT) { - throw new IllegalStateException("Audience validation behavior has already been configured."); - } - audienceVerificationStrategy = AudienceVerificationStrategy.CONTAINS; - requireClaim(PublicClaims.AUDIENCE, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + claims.remove(AUDIENCE_EXACT); + requireClaim(AUDIENCE_CONTAINS, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); return this; } @@ -218,7 +212,7 @@ public JWTVerifier build() { */ public JWTVerifier build(Clock clock) { addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock, audienceVerificationStrategy); + return new JWTVerifier(algorithm, claims, clock); } private void assertPositive(long leeway) { @@ -323,31 +317,36 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok } } - private void verifyClaimValues(DecodedJWT jwt, Map.Entry entry) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { + switch (expectedClaim.getKey()) { + // We use custom keys for audience in the expected claims to differentiate between validating that the audience + // contains all expected values, or validating that the audience contains at least one of the expected values. + case AUDIENCE_EXACT: + assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); + break; + case AUDIENCE_CONTAINS: + assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), false); break; case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + assertValidDateClaim(jwt.getExpiresAt(), (Long) expectedClaim.getValue(), true); break; case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + assertValidDateClaim(jwt.getIssuedAt(), (Long) expectedClaim.getValue(), false); break; case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + assertValidDateClaim(jwt.getNotBefore(), (Long) expectedClaim.getValue(), false); break; case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); + assertValidIssuerClaim(jwt.getIssuer(), (List) expectedClaim.getValue()); break; case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + assertValidStringClaim(expectedClaim.getKey(), jwt.getId(), (String) expectedClaim.getValue()); break; case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); break; default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), expectedClaim.getValue()); break; } } @@ -429,20 +428,10 @@ private void assertDateIsPast(Date date, long leeway, Date today) { } } - private void assertValidAudienceClaim(List audience, List value) { - String invalidMessage = "The Claim 'aud' value doesn't contain the required audience."; - if (audience == null) { - throw new InvalidClaimException(invalidMessage); - } - - if (audienceVerificationStrategy == AudienceVerificationStrategy.CONTAINS) { - if (Collections.disjoint(audience, value)) { - throw new InvalidClaimException(invalidMessage); - } - } else { - if (!audience.containsAll(value)) { - throw new InvalidClaimException(invalidMessage); - } + private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) || + (!shouldContainAll && Collections.disjoint(audience, values))) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } } @@ -467,24 +456,4 @@ public static NonEmptyClaim getInstance() { return nonEmptyClaim; } } - - /** - * Represents how the audience will be validated. - */ - enum AudienceVerificationStrategy { - /** - * No audience validation configured. - */ - UNSET, - - /** - * The JWT audience must match the expected audiences exactly. - */ - EXACT, - - /** - * The JWT audience must contain at least one of the expected audiences. - */ - CONTAINS - } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 2130fcd1..e778d81b 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -27,8 +27,9 @@ public interface Verification { /** * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present * in the "aud" claim. - * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been - * configured with {@link #withAnyOfAudience(String...)} + * + * If this is used in conjunction with {@link #withAnyOfAudience(String...)}, whichever one is configured last will + * determine the audience validation behavior. * * @param audience the required Audience value * @return this same Verification instance. @@ -37,8 +38,9 @@ public interface Verification { /** * Require that the Audience ("aud") claim contain at least one of the specified audiences. - * An {@linkplain IllegalStateException} will be thrown if this Verification instance has already been - * configured with {@link #withAudience(String...)} + * + * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will + * determine the audience validation behavior. * * @apiNote This method was added after the interface was released. * It is defined as a default method for compatibility reasons. diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1847824e..131b389e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -6,6 +6,7 @@ import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.Verification; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -110,7 +111,7 @@ public void shouldThrowOnInvalidSubject() throws Exception { } @Test - public void shouldValidateAudience() throws Exception { + public void shouldAcceptAudienceWhenWithAudienceContainsAll() throws Exception { // Token 'aud': ["Mark"] String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) @@ -120,6 +121,7 @@ public void shouldValidateAudience() throws Exception { assertThat(jwt, is(notNullValue())); + // Token 'aud': ["Mark", "David"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIl19.6WfbIt8m61f9WlCYIQn5CThvw4UNyC66qrPaoinfssw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience("Mark", "David") @@ -130,58 +132,55 @@ public void shouldValidateAudience() throws Exception { } @Test - public void shouldThrowIfConfiguringAllOfAfterAnyOfAudienceValidation() { - exception.expect(IllegalStateException.class); - exception.expectMessage("Audience validation behavior has already been configured."); + public void shouldAllowWithAnyOfAudienceVerificationToOverrideWithAudience() { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark", "Jim"); - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Mark") - .withAudience("Mark") - .build() - .verify(token); - } + Exception exception = null; + try { + verification.build().verify(token); + } catch (Exception e) { + exception = e; - @Test - public void shouldThrowIfConfiguringAnyOfAfterAllOfAudienceValidation() { - exception.expect(IllegalStateException.class); - exception.expectMessage("Audience validation behavior has already been configured."); + } - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("Mark") - .withAnyOfAudience("Mark") - .build() - .verify(token); + assertThat(exception, is(notNullValue())); + assertThat(exception, is(instanceOf(InvalidClaimException.class))); + assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + + DecodedJWT jwt = verification.withAnyOfAudience("Mark", "Jim").build().verify(token); + assertThat(jwt, is(notNullValue())); } @Test - public void shouldAcceptPartialAudience() throws Exception { - //Token 'aud' = ["Mark", "David", "John"] - String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("John") - .build() - .verify(tokenArr); + public void shouldAllowWithAudienceVerificationToOverrideWithAnyOfAudience() { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Jim"); - assertThat(jwtArr, is(notNullValue())); - } + Exception exception = null; + try { + verification.build().verify(token); + } catch (Exception e) { + exception = e; - @Test - public void shouldAcceptOneOfAudience() { - // Token 'aud': ["Mark"] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4"; - DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Mark") - .build() - .verify(token); + } + assertThat(exception, is(notNullValue())); + assertThat(exception, is(instanceOf(InvalidClaimException.class))); + assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + + DecodedJWT jwt = verification.withAudience("Mark").build().verify(token); assertThat(jwt, is(notNullValue())); + } + @Test + public void shouldAcceptAudienceWhenWithAudienceAndPartialExpected() throws Exception { // Token 'aud' = ["Mark", "David", "John"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("John", "Jim") + .withAudience("John") .build() .verify(tokenArr); @@ -189,7 +188,7 @@ public void shouldAcceptOneOfAudience() { } @Test - public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { + public void shouldAcceptAudienceWhenAnyOfAudienceAndAllContained() { // Token 'aud' = ["Mark", "David", "John"] String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret")) @@ -201,7 +200,7 @@ public void shouldAcceptAudienceWhenAnAudienceAndAllContained() { } @Test - public void shouldThrowWhenAudienceHasNoneOfAcceptOneOfAudience() { + public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); @@ -260,21 +259,21 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); verifier = JWTVerifier.init(algorithm) .withAudience((String[]) null) .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); verifier = JWTVerifier.init(algorithm) .withAudience() .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) @@ -282,7 +281,7 @@ public void shouldRemoveAudienceWhenPassingNullReference() throws Exception { .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_EXACT, Collections.singletonList(emptyAud))); } @Test @@ -293,21 +292,21 @@ public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() thro .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience((String[]) null) .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience() .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) @@ -315,7 +314,7 @@ public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() thro .build(); assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("aud", Collections.singletonList(emptyAud))); + assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_CONTAINS, Collections.singletonList(emptyAud))); } @Test @@ -451,7 +450,7 @@ public void shouldThrowOnInvalidCustomClaimValue() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; Map map = new HashMap<>(); map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl(), JWTVerifier.AudienceVerificationStrategy.EXACT); + JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, new ClockImpl()); verifier.verify(token); } From 9ef20ad39eb9bdb6dc53aff88325553c6630accd Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 5 Feb 2021 09:50:18 -0600 Subject: [PATCH 179/355] Remove apiNote and implSpec JavaDoc tags - not available in java8 --- .../java/com/auth0/jwt/interfaces/Verification.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index e778d81b..654b466e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -42,12 +42,12 @@ public interface Verification { * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will * determine the audience validation behavior. * - * @apiNote This method was added after the interface was released. - * It is defined as a default method for compatibility reasons. - * From version 4.0 on, the method will be abstract and all implementations of this interface - * will have to provide their own implementation. + * Note: This method was added after the interface was released. + * It is defined as a default method for compatibility reasons. + * From version 4.0 on, the method will be abstract and all implementations of this interface + * will have to provide their own implementation. * - * @implSpec The default implementation throws an {@linkplain UnsupportedOperationException}. + * The default implementation throws an {@linkplain UnsupportedOperationException}. * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. From 87a66bf7c94afd4b746d05b303cfd120dca96e4a Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 5 Feb 2021 12:55:29 -0600 Subject: [PATCH 180/355] Release 3.13.0 --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2e477e..887528ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [3.13.0](https://github.com/auth0/java-jwt/tree/3.13.0) (2021-02-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.1...3.13.0) + +**Added** +- Add ability to verify audience contains at least one of those expected [\#472](https://github.com/auth0/java-jwt/pull/472) ([jimmyjames](https://github.com/jimmyjames)) +- Add toString to Claim objects [SDK-2225] [\#469](https://github.com/auth0/java-jwt/pull/469) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.12.1](https://github.com/auth0/java-jwt/tree/3.12.1) (2021-01-20) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.0...3.12.1) diff --git a/README.md b/README.md index 90c71bbf..6f1e0e81 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.12.1 + 3.13.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.12.1' +implementation 'com.auth0:java-jwt:3.13.0' ``` ## Available Algorithms From 94f5fa4291948c0b3274959558854ecbbbb3393f Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 19 Feb 2021 14:10:53 -0600 Subject: [PATCH 181/355] Add withPayload to JWTCreator.Builder --- README.md | 10 ++ .../main/java/com/auth0/jwt/JWTCreator.java | 57 +++++- .../java/com/auth0/jwt/JWTCreatorTest.java | 170 +++++++++++++++++- 3 files changed, 230 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6f1e0e81..295dbd73 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,16 @@ String token = JWT.create() .sign(algorithm); ``` +You can also create a JWT by calling `withPayload()` and passing a map of claim names to values: + +```java +Map payloadClaims = new HashMap<>(); +payloadClaims.put("@context", "https://auth0.com/"); +String token = JWT.create() + .withPayload(payloadClaims) + .sign(algorithm); +``` + You can also verify custom Claims on the `JWT.require()` by calling `withClaim()` and passing both the name and the required value. ```java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 340528ad..d0d49189 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -13,10 +13,7 @@ import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; /** @@ -360,6 +357,58 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept return this; } + /** + * Add specific Claims to set as the Payload. If the provided map is null then + * nothing is changed. + *

+ * Accepted types are {@linkplain Map} and {@linkplain List} with basic types + * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, + * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. + * {@linkplain List}s can contain null elements. + *

+ * + *

+ * If any of the claims are invalid, none will be added. + *

+ * + * @param payloadClaims the values to use as Claims in the token's payload. + * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. + * @return this same Builder instance. + */ + public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { + if (payloadClaims == null) { + return this; + } + + if (!validatePayload(payloadClaims)) { + throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + } + + // add claims only after validating all claims so as not to corrupt the claims map of this builder + for (Map.Entry entry : payloadClaims.entrySet()) { + addClaim(entry.getKey(), entry.getValue()); + } + + return this; + } + + private boolean validatePayload(Map payload) { + for (Map.Entry entry : payload.entrySet()) { + String key = entry.getKey(); + assertNonNull(key); + + Object value = entry.getValue(); + if (value instanceof List && !validateClaim((List) value)) { + return false; + } else if (value instanceof Map && !validateClaim((Map) value)) { + return false; + } else if (value != null && !isSupportedType(value)) { + return false; + } + } + return true; + } + private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 2d9179ff..a1aea9b5 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -15,9 +15,7 @@ import java.security.interfaces.RSAPrivateKey; import java.util.*; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -717,4 +715,170 @@ public void shouldRefuseCustomListClaimForUnknownArrayType() throws Exception { .sign(Algorithm.HMAC256("secret")); } + @Test + public void withPayloadShouldAddBasicClaim() { + Map payload = new HashMap<>(); + payload.put("asd", 123); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("asd", 123)); + } + + @Test + public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { + String jwt = JWTCreator.init() + .withPayload(null) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, is("{}")); + } + + @Test + public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKey() { + Map payload = new HashMap<>(); + payload.put(PublicClaims.KEY_ID, "xyz"); + + String jwt = JWTCreator.init() + .withKeyId("abc") + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + } + + @Test + public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { + Map payload = new HashMap<>(); + payload.put(PublicClaims.ISSUER, "xyz"); + + String jwt = JWTCreator.init() + .withPayload(payload) + .withIssuer("abc") + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); + } + + @Test + public void shouldRemovePayloadIfTheValueIsNull() throws Exception { + String jwt = JWTCreator.init() + .withClaim("key", "stubValue") + .withPayload(Collections.singletonMap("key", (Map) null)) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + + String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + ObjectMapper mapper = new ObjectMapper(); + Map map = (Map) mapper.readValue(body, Map.class); + assertThat(map, anEmptyMap()); + } + + @Test + public void withPayloadShouldNotAllowCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("entry", "value"); + payload.put("pojo", new UserPojo("name", 42)); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldAllowNullListItems() { + Map payload = new HashMap<>(); + payload.put("list", Arrays.asList("item1", null, "item2")); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("list", Arrays.asList("item1", null, "item2"))); + } + + @Test + public void withPayloadShouldNotAllowListWithCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldNotAllowMapWithCustomType() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + + Map payload = new HashMap<>(); + payload.put("entry", "value"); + payload.put("map", Collections.singletonMap("pojo", new UserPojo("name", 42))); + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + } + + @Test + public void withPayloadShouldAllowNestedSupportedTypes() { + /* + JWT: + { + "stringClaim": "string", + "intClaim": 41, + "listClaim": [ + 1, 2, { + "nestedObjKey": true + } + ], + "objClaim": { + "objKey": ["nestedList1", "nestedList2"] + } + } + */ + + List listClaim = Arrays.asList(1, 2, Collections.singletonMap("nestedObjKey", "nestedObjValue")); + Map mapClaim = new HashMap<>(); + mapClaim.put("objKey", Arrays.asList("nestedList1", true)); + + Map payload = new HashMap<>(); + payload.put("stringClaim", "string"); + payload.put("intClaim", 41); + payload.put("listClaim", listClaim); + payload.put("objClaim", mapClaim); + + String jwt = JWTCreator.init() + .withPayload(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", "string")); + assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", 41)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", mapClaim)); + } } From fbbf140efc45854acbdc375fee497ff97fa4ff47 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 19 Feb 2021 14:21:50 -0600 Subject: [PATCH 182/355] Use wildcard instead of Object --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index d0d49189..0280af07 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -375,7 +375,7 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. * @return this same Builder instance. */ - public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { + public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { if (payloadClaims == null) { return this; } @@ -385,15 +385,15 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgu } // add claims only after validating all claims so as not to corrupt the claims map of this builder - for (Map.Entry entry : payloadClaims.entrySet()) { + for (Map.Entry entry : payloadClaims.entrySet()) { addClaim(entry.getKey(), entry.getValue()); } return this; } - private boolean validatePayload(Map payload) { - for (Map.Entry entry : payload.entrySet()) { + private boolean validatePayload(Map payload) { + for (Map.Entry entry : payload.entrySet()) { String key = entry.getKey(); assertNonNull(key); From 9987b98237f2988ea4f150e9fc1d91be6b31b582 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 24 Feb 2021 14:42:54 -0600 Subject: [PATCH 183/355] Update README to include HMAC key length recommendations --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 295dbd73..1bff22d1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). +##### HMAC Key Length and Security + +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. + #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: From 6b732d3931796e2789254301575f1fc0a8c231a7 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 25 Feb 2021 10:05:01 -0600 Subject: [PATCH 184/355] Add ability to run apiDiff for incompatible changes (#476) * Add ability to run apiDiff for incompatible changes * Add api diff to CI --- .circleci/config.yml | 57 +++++++++++++++++++++++++++++++++++--------- lib/build.gradle | 1 + settings.gradle | 2 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9bb01639..78c9dab4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,7 @@ -version: 2 -jobs: - build: - docker: - - image: openjdk:8-jdk +version: 2.1 + +commands: + checkout-and-build: steps: - checkout - run: chmod +x gradlew @@ -12,17 +11,53 @@ jobs: - v1-dependencies-{{ checksum "build.gradle" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - # run tests! - - run: ./gradlew clean check jacocoTestReport --continue --console=plain - - run: - name: Upload Coverage - when: on_success - command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + - run: ./gradlew clean build - save_cache: paths: - ~/.m2 key: v1-dependencies-{{ checksum "build.gradle" }} + run-tests: + steps: + - run: ./gradlew check jacocoTestReport --continue --console=plain + - run: + name: Upload Coverage + when: on_success + command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html + +jobs: + build: + docker: + - image: openjdk:8-jdk + steps: + - checkout-and-build + - run-tests + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb + api-diff: + docker: + - image: openjdk:8-jdk + steps: + - checkout-and-build + - run-api-diff environment: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb + +workflows: + build-and-test: + jobs: + - build + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index fd920e2c..209e90ce 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -12,6 +12,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" + baselineCompareVersion "3.13.0" developers { auth0 { diff --git a/settings.gradle b/settings.gradle index d3c7fb14..2170ea25 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { } plugins { id 'com.jfrog.bintray' version '1.8.5' - id 'com.auth0.gradle.oss-library.java' version '0.12.1' + id 'com.auth0.gradle.oss-library.java' version '0.14.0' } } From fb51a2f147cfa95cbb64d7990b775cf11a64e6d6 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Fri, 26 Feb 2021 12:22:01 -0600 Subject: [PATCH 185/355] Release 3.14.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 887528ab..92116fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.14.0](https://github.com/auth0/java-jwt/tree/3.14.0) (2021-02-26) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.13.0...3.14.0) + +**Added** +- Add withPayload to JWTCreator.Builder [\#475](https://github.com/auth0/java-jwt/pull/475) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.13.0](https://github.com/auth0/java-jwt/tree/3.13.0) (2021-02-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.12.1...3.13.0) diff --git a/README.md b/README.md index 1bff22d1..65e033fa 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.13.0 + 3.14.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.13.0' +implementation 'com.auth0:java-jwt:3.14.0' ``` ## Available Algorithms From 7dea6ac54d5b5b8822a9f3ee41cc4666e250cc27 Mon Sep 17 00:00:00 2001 From: XakepSDK Date: Mon, 8 Mar 2021 15:56:13 +0000 Subject: [PATCH 186/355] Move form commons-codec Base64 to j.u.Base64 (#478) * Use Java Base64 encoding and decoding fix last usages of commons-codec Remove codec usages Remove commons-codec * Throw on invalid Base64 encoded tokens * Fix broken tests that became broken after java.util.Base64 transition Co-authored-by: jimmyjames Co-authored-by: Xakep_SDK --- lib/build.gradle | 1 - .../main/java/com/auth0/jwt/JWTCreator.java | 9 ++-- .../main/java/com/auth0/jwt/JWTDecoder.java | 8 +-- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 7 ++- .../auth0/jwt/algorithms/HMACAlgorithm.java | 7 ++- .../auth0/jwt/algorithms/NoneAlgorithm.java | 14 +++-- .../auth0/jwt/algorithms/RSAAlgorithm.java | 7 ++- .../java/com/auth0/jwt/JWTCreatorTest.java | 54 +++++++++---------- .../java/com/auth0/jwt/JWTDecoderTest.java | 27 ++++++++-- lib/src/test/java/com/auth0/jwt/JWTTest.java | 22 ++++---- .../jwt/algorithms/CryptoTestHelper.java | 11 ++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 53 ++++++++++-------- .../ECDSABouncyCastleProviderTests.java | 45 ++++++++-------- .../jwt/algorithms/HMACAlgorithmTest.java | 16 +++++- .../jwt/algorithms/NoneAlgorithmTest.java | 13 ++++- .../jwt/algorithms/RSAAlgorithmTest.java | 9 ++++ 16 files changed, 183 insertions(+), 120 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 209e90ce..e0894e3d 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -43,7 +43,6 @@ compileJava { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' - implementation 'commons-codec:commons-codec:1.14' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 0280af07..26df771f 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.util.*; @@ -27,7 +26,7 @@ public final class JWTCreator { private final Algorithm algorithm; private final String headerJson; private final String payloadJson; - + private static final ObjectMapper mapper; private static final SimpleModule module; @@ -492,11 +491,11 @@ private void addClaim(String name, Object value) { } private String sign() throws SignatureGenerationException { - String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8)); - String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); - String signature = Base64.encodeBase64URLSafeString((signatureBytes)); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes)); return String.format("%s.%s.%s", header, payload, signature); } diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 445bf95d..71acbe59 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -6,10 +6,10 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Header; import com.auth0.jwt.interfaces.Payload; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.StringUtils; import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Date; import java.util.List; import java.util.Map; @@ -37,8 +37,8 @@ final class JWTDecoder implements DecodedJWT, Serializable { String headerJson; String payloadJson; try { - headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0])); - payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1])); + headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); } catch (NullPointerException e) { throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e); } catch (IllegalArgumentException e){ 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 6d065c9c..c2d08a15 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -4,13 +4,13 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECDSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.util.Base64; /** * Subclass representing an Elliptic Curve signing algorithm @@ -40,9 +40,8 @@ class ECDSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); @@ -52,7 +51,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 596f907e..c5792457 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -3,12 +3,12 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Base64; /** * Subclass representing an Hash-based MAC signing algorithm @@ -48,14 +48,13 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); boolean valid = crypto.verifySignatureFor(getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } - } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException e) { + } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index f70b72b2..076a6b94 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -3,7 +3,8 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; + +import java.util.Base64; class NoneAlgorithm extends Algorithm { @@ -13,9 +14,14 @@ class NoneAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - if (signatureBytes.length > 0) { - throw new SignatureVerificationException(this); + try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); + + if (signatureBytes.length > 0) { + throw new SignatureVerificationException(this); + } + } catch (IllegalArgumentException e) { + throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index a61fc97e..31773a09 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -4,7 +4,6 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; -import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -12,6 +11,7 @@ import java.security.SignatureException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.Base64; /** * Subclass representing an RSA signing algorithm @@ -39,9 +39,8 @@ class RSAAlgorithm extends Algorithm { @Override public void verify(DecodedJWT jwt) throws SignatureVerificationException { - byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature()); - try { + byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId()); if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); @@ -50,7 +49,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index a1aea9b5..eabc2f31 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -5,7 +5,7 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.codec.binary.Base64; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -49,7 +49,7 @@ public void shouldAddHeaderClaim() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("asd", 123)); } @@ -74,7 +74,7 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() throws assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); } @@ -90,7 +90,7 @@ public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() throws Exce assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "abc")); } @@ -107,7 +107,7 @@ public void shouldRemoveHeaderIfTheValueIsNull() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.isNotPresent(PublicClaims.KEY_ID)); assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); } @@ -120,7 +120,7 @@ public void shouldAddKeyId() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed")); } @@ -136,7 +136,7 @@ public void shouldAddKeyIdIfAvailableFromRSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -153,7 +153,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -169,7 +169,7 @@ public void shouldAddKeyIdIfAvailableFromECDSAKAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -186,7 +186,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromECDSAKAlgorithms() throws Exceptio assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -202,7 +202,7 @@ public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -219,7 +219,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromECDSAAlgorithms() throws Exception assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id")); } @@ -319,7 +319,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); } @@ -330,7 +330,7 @@ public void shouldSetDefaultTypeInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); } @@ -343,7 +343,7 @@ public void shouldSetCustomTypeInTheHeader() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("typ", "passport")); } @@ -521,7 +521,7 @@ public void shouldAcceptCustomMapClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class).get("data"); @@ -574,8 +574,8 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); List list = (List) mapper.readValue(body, Map.class).get("data"); @@ -618,7 +618,7 @@ public void shouldAcceptCustomClaimWithNullMapAndRemoveClaim() throws Exception assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -635,7 +635,7 @@ public void shouldAcceptCustomClaimWithNullListAndRemoveClaim() throws Exception assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -725,7 +725,7 @@ public void withPayloadShouldAddBasicClaim() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("asd", 123)); } @@ -737,7 +737,7 @@ public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, is("{}")); } @@ -753,7 +753,7 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); } @@ -769,7 +769,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); } @@ -783,7 +783,7 @@ public void shouldRemovePayloadIfTheValueIsNull() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String body = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map map = (Map) mapper.readValue(body, Map.class); assertThat(map, anEmptyMap()); @@ -812,7 +812,7 @@ public void withPayloadShouldAllowNullListItems() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("list", Arrays.asList("item1", null, "item2"))); } @@ -875,7 +875,7 @@ public void withPayloadShouldAllowNestedSupportedTypes() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - String payloadJson = new String(Base64.decodeBase64(parts[1]), StandardCharsets.UTF_8); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", "string")); assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", 41)); assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index efd1b398..5cfe069e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -4,7 +4,6 @@ import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; import org.junit.Assert; @@ -14,6 +13,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Date; import java.util.Map; @@ -35,7 +35,8 @@ public void getSubject() throws Exception { @Test public void shouldThrowIfTheContentIsNotProperlyEncoded() throws Exception { exception.expect(JWTDecodeException.class); - exception.expectMessage("The input is not a valid base 64 encoded string."); + exception.expectMessage(startsWith("The string '")); + exception.expectMessage(endsWith("' doesn't have a valid JSON format.")); JWT.decode("eyJ0eXAiOiJKV1QiLCJhbGciO-corrupted.eyJ0ZXN0IjoxMjN9.sLtFC2rLAzN0-UJ13OLQX6ezNptAQzespaOGwCnpqk"); } @@ -71,6 +72,24 @@ public void shouldThrowIfHeaderHasInvalidJSONFormat() throws Exception { customJWT(invalidJson, validJson, "signature"); } + @Test + public void shouldThrowWhenHeaderNotValidBase64() { + exception.expect(JWTDecodeException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25l+IiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.Ox-WRXRaGAuWt2KfPvWiGcCrPqZtbp_4OnQzZXaTfss"; + JWT.decode(jwt); + } + + @Test + public void shouldThrowWhenPayloadNotValidBase64() { + exception.expect(JWTDecodeException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRo+MCJ9.Ox-WRXRaGAuWt2KfPvWiGcCrPqZtbp_4OnQzZXaTfss"; + JWT.decode(jwt); + } + // Parts @Test @@ -328,8 +347,8 @@ public void shouldSerializeAndDeserialize() throws Exception { //Helper Methods private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) { - String header = Base64.encodeBase64URLSafeString(jsonHeader.getBytes(StandardCharsets.UTF_8)); - String body = Base64.encodeBase64URLSafeString(jsonPayload.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(jsonHeader.getBytes(StandardCharsets.UTF_8)); + String body = Base64.getUrlEncoder().withoutPadding().encodeToString(jsonPayload.getBytes(StandardCharsets.UTF_8)); return JWT.decode(String.format("%s.%s.%s", header, body, signature)); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index b778be13..bcf62e05 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Clock; import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.codec.binary.Base64; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.IsCollectionContaining; import org.junit.Rule; @@ -15,6 +14,7 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; +import java.util.Base64; import java.util.Date; import static org.hamcrest.Matchers.*; @@ -393,7 +393,7 @@ public void shouldCreateAnEmptyHMAC256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -409,7 +409,7 @@ public void shouldCreateAnEmptyHMAC384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -425,7 +425,7 @@ public void shouldCreateAnEmptyHMAC512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -441,7 +441,7 @@ public void shouldCreateAnEmptyRSA256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -457,7 +457,7 @@ public void shouldCreateAnEmptyRSA384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -473,7 +473,7 @@ public void shouldCreateAnEmptyRSA512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "RS512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -489,7 +489,7 @@ public void shouldCreateAnEmptyECDSA256SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -508,7 +508,7 @@ public void shouldCreateAnEmptyECDSA256KSignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES256K")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -524,7 +524,7 @@ public void shouldCreateAnEmptyECDSA384SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES384")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); @@ -540,7 +540,7 @@ public void shouldCreateAnEmptyECDSA512SignedToken() throws Exception { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); - String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); assertThat(headerJson, JsonMatcher.hasEntry("alg", "ES512")); assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT")); assertThat(parts[1], is("e30")); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java index f977e42a..25b08d70 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/CryptoTestHelper.java @@ -1,14 +1,13 @@ package com.auth0.jwt.algorithms; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.codec.binary.Base64; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public abstract class CryptoTestHelper { @@ -16,7 +15,7 @@ public abstract class CryptoTestHelper { public static String asJWT(Algorithm algorithm, String header, String payload) { byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); - String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); return String.format("%s.%s.%s", header, payload, jwtSignature); } 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 7f7b9c94..39c9dabf 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -4,8 +4,6 @@ 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.hamcrest.Matchers; import org.hamcrest.collection.IsIn; import org.junit.Assert; @@ -20,13 +18,15 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.util.Arrays; +import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; import static org.mockito.ArgumentMatchers.any; @@ -156,7 +156,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[63]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -169,7 +169,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -183,7 +183,7 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -260,8 +260,8 @@ public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() throws Exceptio public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() 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."))); + exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(hasMessage(is("Last unit does not have enough valid bits"))); String jwt = ES256K_JWT.substring(0,ES256K_JWT.length()-1); Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); @@ -275,7 +275,7 @@ public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() throws Excep byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -289,7 +289,7 @@ public void shouldFailECDSA256KVerificationOnInvalidDERSignature() throws Except byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -386,7 +386,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[95]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -399,7 +399,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -413,7 +413,7 @@ public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -511,7 +511,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[131]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -524,7 +524,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -538,7 +538,7 @@ public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -553,7 +553,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E byte[] bytes = new byte[256]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -617,7 +617,18 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception algorithm.verify(JWT.decode(jwt)); } - //Sign + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4+EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + Algorithm algorithm = Algorithm.ECDSA512(key); + algorithm.verify(JWT.decode(jwt)); + } + + //Sign private static final String ES256Header = "eyJhbGciOiJFUzI1NiJ9"; private static final String ES384Header = "eyJhbGciOiJFUzM4NCJ9"; private static final String ES512Header = "eyJhbGciOiJFUzUxMiJ9"; @@ -643,7 +654,7 @@ public void shouldDoECDSA256Signing() throws Exception { public void shouldDoECDSA256SigningWithBothKeys() throws Exception { Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); byte[] signatureBytes = algorithm.sign(ES256HeaderBytes, auth0IssPayloadBytes); - String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwtSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); String jwt = String.format("%s.%s.%s", ES256Header, auth0IssPayload, jwtSignature); assertSignaturePresent(jwt); @@ -1267,12 +1278,12 @@ public void shouldBeEqualSignatureMethodDecodeResults() throws Exception { bout.write('.'); bout.write(payloadBytes); - String jwtSignature1 = Base64.encodeBase64URLSafeString(algorithm.sign(bout.toByteArray())); + String jwtSignature1 = Base64.getUrlEncoder().withoutPadding().encodeToString(algorithm.sign(bout.toByteArray())); String jwt1 = String.format("%s.%s.%s", header, payload, jwtSignature1); algorithm.verify(JWT.decode(jwt1)); - String jwtSignature2 = Base64.encodeBase64URLSafeString(algorithm.sign(headerBytes, payloadBytes)); + String jwtSignature2 = Base64.getUrlEncoder().withoutPadding().encodeToString(algorithm.sign(headerBytes, payloadBytes)); String jwt2 = String.format("%s.%s.%s", header, payload, jwtSignature2); algorithm.verify(JWT.decode(jwt2)); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java index dbecae1e..68fe6e50 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -1,11 +1,9 @@ package com.auth0.jwt.algorithms; -import static com.auth0.jwt.algorithms.CryptoTestHelper.*; 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; @@ -18,9 +16,12 @@ import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; +import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; +import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; import static com.auth0.jwt.algorithms.ECDSAAlgorithmTest.*; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; @@ -150,8 +151,8 @@ public void shouldFailECDSA256KVerificationWhenUsingPrivateKey() throws Exceptio public void shouldFailECDSA256KVerificationOnInvalidJOSESignatureLength() 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."))); + exception.expectCause(isA(IllegalArgumentException.class)); + exception.expectCause(hasMessage(is("Last unit does not have enough valid bits"))); String jwt = ES256K_JWT.substring(0,ES256K_JWT.length()-1); Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); @@ -165,7 +166,7 @@ public void shouldFailECDSA256KVerificationOnInvalidJOSESignature() throws Excep byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -179,7 +180,7 @@ public void shouldFailECDSA256KVerificationOnInvalidDERSignature() throws Except byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NksifQo.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256K((ECPublicKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256K, "EC"), null); algorithm.verify(JWT.decode(jwt)); @@ -200,7 +201,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); Algorithm algorithm = Algorithm.ECDSA256(key); algorithm.verify(JWT.decode(jwt)); @@ -220,7 +221,7 @@ public void shouldThrowOnECDSA256VerificationWithDERSignatureWithBothKeys() thro exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jShFPj0hpCWn7x1nhxPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -277,7 +278,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[63]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -290,7 +291,7 @@ public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[64]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -304,7 +305,7 @@ public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[64]; bytes[0] = 0x30; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -325,7 +326,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignature() throws Exception 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=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UtccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); Algorithm algorithm = Algorithm.ECDSA384(key); algorithm.verify(JWT.decode(jwt)); @@ -345,7 +346,7 @@ public void shouldThrowOnECDSA384VerificationWithDERSignatureWithBothKeys() thro 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=="; + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXBKRjyNAEqm4dmh7ohkEmbk2gHxtH6GdGDq2L4IduahG2UtccCMH8CE2vHCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAurDEv8w"; Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -402,7 +403,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[95]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -415,7 +416,7 @@ public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -429,7 +430,7 @@ public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[96]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -450,7 +451,7 @@ public void shouldThrowOnECDSA512VerificationWithDERSignature() throws Exception exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); Algorithm algorithm = Algorithm.ECDSA512(key); algorithm.verify(JWT.decode(jwt)); @@ -470,7 +471,7 @@ public void shouldThrowECDSA512VerificationWithDERSignatureWithBothKeys() throws exception.expectCause(isA(SignatureException.class)); exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); - String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0UW726GsDVCsb4RTFeUTTrKaHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0mmWFhVCR1YNg"; Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); } @@ -527,7 +528,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws byte[] bytes = new byte[131]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -540,7 +541,7 @@ public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Except byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -554,7 +555,7 @@ public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Excepti byte[] bytes = new byte[132]; new SecureRandom().nextBytes(bytes); bytes[0] = 0x30; - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -569,7 +570,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E byte[] bytes = new byte[256]; new SecureRandom().nextBytes(bytes); - String signature = Base64.encodeBase64URLSafeString(bytes); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 38aaf301..fd5aed71 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -293,4 +293,18 @@ public void shouldBeEqualSignatureMethodResults() throws Exception { assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); } -} + + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.verifySignatureFor(anyString(), any(byte[].class), any(String.class), any(String.class), any(byte[].class))) + .thenThrow(NoSuchAlgorithmException.class); + + Algorithm algorithm = new HMACAlgorithm(crypto, "some-alg", "some-algorithm", "secret".getBytes(StandardCharsets.UTF_8)); + String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWm+i903JuUoDRZDBPB7HwkS4nVyWH1M"; + algorithm.verify(JWT.decode(jwt)); + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java index f6e72b84..9c528f68 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/NoneAlgorithmTest.java @@ -7,8 +7,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; public class NoneAlgorithmTest { @@ -45,4 +44,14 @@ public void shouldFailNoneVerificationWhenSignatureIsPresent() throws Exception public void shouldReturnNullSigningKeyId() throws Exception { assertThat(Algorithm.none().getSigningKeyId(), is(nullValue())); } + + @Test + public void shouldThrowWhenSignatureNotValidBase64() { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.Ox-WRXRaGAuWt2KfPvW+iGcCrPqZtbp_4OnQzZXaTfss"; + Algorithm algorithm = Algorithm.none(); + algorithm.verify(JWT.decode(jwt)); + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java index b24365e3..a4902781 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/RSAAlgorithmTest.java @@ -569,4 +569,13 @@ public void shouldFailOnRSA256SigningWithDeprecatedMethodWhenProvidedPrivateKeyI algorithm.sign(new byte[0]); } + @Test + public void shouldThrowWhenSignatureNotValidBase64() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(IllegalArgumentException.class)); + + String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNu+LAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA"; + Algorithm algorithm = Algorithm.RSA256((RSAKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE, "RSA")); + algorithm.verify(JWT.decode(jwt)); + } } \ No newline at end of file From 523b2d6cc091ab0026bef4ad2b72f7f502babd68 Mon Sep 17 00:00:00 2001 From: Josiah Boning Date: Thu, 25 Mar 2021 22:35:10 -0700 Subject: [PATCH 187/355] Update Java version in README.md Java 8 has been required since #457. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65e033fa..871bdcfa 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. -> This library currently supports Java 7. Beginning soon, it will require Java 8 as the minimum supported Java version. See [this issue](https://github.com/auth0/java-jwt/issues/457) for additional information and timelines. +> This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. ## Installation From 603e2b738f185be6a9a4036c2c27634d5d750029 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 31 Mar 2021 01:33:13 +0200 Subject: [PATCH 188/355] Improve README formatting --- README.md | 98 +++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 871bdcfa..b86ad957 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) -[![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](http://doge.mit-license.org) +[![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org) [![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). @@ -122,31 +122,31 @@ You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. U * Example using `HS256` -```java -try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); -} catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. -} -``` + ```java + try { + Algorithm algorithm = Algorithm.HMAC256("secret"); + String token = JWT.create() + .withIssuer("auth0") + .sign(algorithm); + } catch (JWTCreationException exception){ + //Invalid Signing configuration / Couldn't convert Claims. + } + ``` * Example using `RS256` -```java -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); -} catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. -} -``` + ```java + RSAPublicKey publicKey = //Get the key instance + RSAPrivateKey privateKey = //Get the key instance + try { + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + String token = JWT.create() + .withIssuer("auth0") + .sign(algorithm); + } catch (JWTCreationException exception){ + //Invalid Signing configuration / Couldn't convert Claims. + } + ``` If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. @@ -157,35 +157,35 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` * Example using `HS256` -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); -} catch (JWTVerificationException exception){ - //Invalid signature/claims -} -``` + ```java + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + try { + Algorithm algorithm = Algorithm.HMAC256("secret"); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("auth0") + .build(); //Reusable verifier instance + DecodedJWT jwt = verifier.verify(token); + } catch (JWTVerificationException exception){ + //Invalid signature/claims + } + ``` * Example using `RS256` -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); -} catch (JWTVerificationException exception){ - //Invalid signature/claims -} -``` + ```java + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + RSAPublicKey publicKey = //Get the key instance + RSAPrivateKey privateKey = //Get the key instance + try { + Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("auth0") + .build(); //Reusable verifier instance + DecodedJWT jwt = verifier.verify(token); + } catch (JWTVerificationException exception){ + //Invalid signature/claims + } + ``` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. From 35570e7ef3c036fc3e13a2fb8372a095f73d9f94 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Tue, 9 Mar 2021 19:35:05 -0600 Subject: [PATCH 189/355] remove jcenter --- build.gradle | 2 +- settings.gradle | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index bf6dfe4f..c938b200 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,6 @@ allprojects { group = 'com.auth0' repositories { - jcenter() + mavenCentral() } } diff --git a/settings.gradle b/settings.gradle index 2170ea25..962399d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,6 @@ pluginManagement { repositories { gradlePluginPortal() - jcenter() } plugins { id 'com.jfrog.bintray' version '1.8.5' From 6ce1991f9b35a69c1e49dba2b12909f1fc86fb4c Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 5 Apr 2021 12:03:53 -0500 Subject: [PATCH 190/355] Release 3.15.0 --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92116fa2..3cf74f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [3.15.0](https://github.com/auth0/java-jwt/tree/3.15.0) (2021-04-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.14.0...3.15.0) + +**Changed** +- Remove jcenter [\#482](https://github.com/auth0/java-jwt/pull/482) ([jimmyjames](https://github.com/jimmyjames)) +- Move form commons-codec Base64 to j.u.Base64 [\#478](https://github.com/auth0/java-jwt/pull/478) ([XakepSDK](https://github.com/XakepSDK)) + ## [3.14.0](https://github.com/auth0/java-jwt/tree/3.14.0) (2021-02-26) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.13.0...3.14.0) diff --git a/README.md b/README.md index b86ad957..2c26f951 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.14.0 + 3.15.0 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.14.0' +implementation 'com.auth0:java-jwt:3.15.0' ``` ## Available Algorithms From 75272c3343d7e058843e164abc29e3f42a202409 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 5 Apr 2021 12:20:40 -0500 Subject: [PATCH 191/355] Update OSS plugin version --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 962399d3..7faf41ff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { } plugins { id 'com.jfrog.bintray' version '1.8.5' - id 'com.auth0.gradle.oss-library.java' version '0.14.0' + id 'com.auth0.gradle.oss-library.java' version '0.15.1' } } From 6ded84d9c490b06d9f9f6be09c34f441b2317798 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Thu, 8 Apr 2021 18:04:47 +0200 Subject: [PATCH 192/355] delete duplicated template --- .github/ISSUE_TEMPLATE/report_a_bug.md | 55 -------------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/report_a_bug.md diff --git a/.github/ISSUE_TEMPLATE/report_a_bug.md b/.github/ISSUE_TEMPLATE/report_a_bug.md deleted file mode 100644 index 50b9fa7e..00000000 --- a/.github/ISSUE_TEMPLATE/report_a_bug.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Report a bug -about: Have you found a bug or issue? Create a bug report for this SDK -title: '' -labels: bug report -assignees: '' ---- - - - -### Describe the problem - - - -### What was the expected behavior? - - - -### Reproduction - - -- Step 1.. -- Step 2.. -- ... - -### Environment - - - -- **Version of this library used:** -- **Which framework are you using, if applicable:** -- **Other modules/plugins/libraries that might be involved:** -- **Any other relevant information you think would be useful:** \ No newline at end of file From a643cebdc713acdcf790b7f17f57c4aea2211987 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:04:30 +0200 Subject: [PATCH 193/355] Add Eclipse files to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 5ab9b34a..b5766efd 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,11 @@ crashlytics.properties crashlytics-build.properties fabric.properties +# Eclipse IDE +.classpath +.project +.settings/ + ### Java template *.class From 85bf1534586c8e8858b7bdf1a5dbd5f200574f52 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:14:51 +0200 Subject: [PATCH 194/355] Add package-info.java for internal `impl` package --- lib/src/main/java/com/auth0/jwt/impl/NullClaim.java | 2 +- lib/src/main/java/com/auth0/jwt/impl/package-info.java | 7 +++++++ lib/src/main/java/com/auth0/jwt/interfaces/Header.java | 3 ++- lib/src/main/java/com/auth0/jwt/interfaces/Payload.java | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/impl/package-info.java diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 8d10ca10..2e9a38c2 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -8,7 +8,7 @@ import java.util.Map; /** - * The {@link NullClaim} class is a Claim implementation that returns null when any of it's methods it's called. + * The {@code NullClaim} class is a Claim implementation that returns null when any of it's methods is called. */ public class NullClaim implements Claim { @Override diff --git a/lib/src/main/java/com/auth0/jwt/impl/package-info.java b/lib/src/main/java/com/auth0/jwt/impl/package-info.java new file mode 100644 index 00000000..334ccb8a --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/impl/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains parts of the internal implementation of this library. + * + *

Do not use any of the classes in this package. They might be removed + * or changed at any point without prior warning. + */ +package com.auth0.jwt.impl; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java index 0a83d1ce..030643d0 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java @@ -34,7 +34,8 @@ public interface Header { String getKeyId(); /** - * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a NullClaim will be returned. + * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a 'null claim' will be + * returned. All of the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java index 0f639ab9..b999ad43 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java @@ -59,7 +59,8 @@ public interface Payload { String getId(); /** - * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a NullClaim will be returned. + * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a 'null claim' + * will be returned. All of the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. From e745182d847eb162a96f3967265aba9b916c842d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 31 Mar 2021 02:01:11 +0200 Subject: [PATCH 195/355] Use JDK 16 for javadoc generation This allows using the latest javadoc features (such as a search bar) without having to the change the lowest Java version supported by the project. --- lib/build.gradle | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index e0894e3d..e394a18e 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -30,15 +30,30 @@ oss { } } +def javaVersion = 8 java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(javaVersion) } } compileJava { - sourceCompatibility '1.8' - targetCompatibility '1.8' + sourceCompatibility "$javaVersion" + targetCompatibility "$javaVersion" +} + +// Use latest JDK for javadoc generation +tasks.withType(Javadoc).configureEach { + javadocTool = javaToolchains.javadocToolFor { + languageVersion = JavaLanguageVersion.of(16) + } +} + +javadoc { + // Specify Java version this project uses + // Otherwise would use version of javadoc toolchain by default which could + // cause compilation error mismatch between compileJava and javadoc creation + options.addStringOption('-release', "$javaVersion") } dependencies { From 64cb404a816cc9f5f8523f7b2a348547cf024c80 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Apr 2021 18:48:13 +0200 Subject: [PATCH 196/355] Exclude internal `impl` package from javadoc --- lib/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/build.gradle b/lib/build.gradle index e394a18e..851ed055 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -50,6 +50,8 @@ tasks.withType(Javadoc).configureEach { } javadoc { + // Exclude internal implementation package from javadoc + excludes = ['com/auth0/jwt/impl'] // Specify Java version this project uses // Otherwise would use version of javadoc toolchain by default which could // cause compilation error mismatch between compileJava and javadoc creation From 7d1035cf79a21d1fc3c068b527bd230f07ac8ca4 Mon Sep 17 00:00:00 2001 From: Jeffrey Swan <61218880+plan-do-break-fix@users.noreply.github.com> Date: Thu, 29 Apr 2021 09:04:14 -0500 Subject: [PATCH 197/355] fix(docs) - corrects typo in project documentation (#494) Co-authored-by: Jim Anderson --- .github/ISSUE_TEMPLATE/report-a-bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md index 60505a3f..e5cf8c46 100644 --- a/.github/ISSUE_TEMPLATE/report-a-bug.md +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -29,7 +29,7 @@ By submitting an Issue to this repository, you agree to the terms within the Aut ### Reproduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index a672afaf..001963bd 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -2,6 +2,12 @@ plugins { id 'java' id 'jacoco' id 'com.auth0.gradle.oss-library.java' + id 'checkstyle' +} + +checkstyle { + toolVersion '10.0' + checkstyleTest.enabled = false //We are disabling lint checks for tests } logger.lifecycle("Using version ${version} for ${group}.${name}") diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index ca05deff..9d719d1e 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -6,6 +6,9 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; +/** + * Exposes all the JWT functionalities. + */ @SuppressWarnings("WeakerAccess") public class JWT { @@ -22,11 +25,13 @@ public JWT() { /** * Decode a given Json Web Token. *

- * Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. + * Note that this method doesn't verify the token's signature! + * Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. * @return a decoded JWT. - * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. + * @throws JWTDecodeException if any part of the token contained an invalid jwt + * or JSON format of each of the jwt parts. */ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { return new JWTDecoder(parser, token); @@ -35,11 +40,13 @@ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { /** * Decode a given Json Web Token. *

- * Note that this method doesn't verify the token's signature! Use it only if you trust the token or you already verified it. + * Note that this method doesn't verify the token's signature! + * Use it only if you trust the token or you already verified it. * * @param token with jwt format as string. * @return a decoded JWT. - * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts. + * @throws JWTDecodeException if any part of the token contained an invalid jwt + * or JSON format of each of the jwt parts. */ public static DecodedJWT decode(String token) throws JWTDecodeException { return new JWTDecoder(token); @@ -57,7 +64,7 @@ public static Verification require(Algorithm algorithm) { } /** - * Returns a Json Web Token builder used to create and sign tokens + * Returns a Json Web Token builder used to create and sign tokens. * * @return a token builder. */ diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index ca6e3918..6b355b36 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -15,7 +15,8 @@ import java.util.Map.Entry; /** - * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) from a given Header and Payload content. + * The JWTCreator class holds the sign method to generate a complete JWT (with Signature) + * from a given Header and Payload content. *

* This class is thread-safe. */ @@ -38,7 +39,8 @@ public final class JWTCreator { mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); } - private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) throws JWTCreationException { + private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) + throws JWTCreationException { this.algorithm = algorithm; try { headerJson = mapper.writeValueAsString(new HeaderClaimsHolder(headerClaims)); @@ -96,7 +98,8 @@ public Builder withHeader(Map headerClaims) { /** * Add a specific Key Id ("kid") claim to the Header. - * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, the 'kid' value will be taken from that provider and this one will be ignored. + * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, + * the 'kid' value will be taken from that provider and this one will be ignored. * * @param keyId the Key Id value. * @return this same Builder instance. @@ -322,48 +325,6 @@ public Builder withClaim(String name, Instant value) throws IllegalArgumentExcep return this; } - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. - */ - public Builder withArrayClaim(String name, String[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null. - */ - public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - - /** - * Add a custom Array Claim with the given items. - * - * @param name the Claim's name. - * @param items the Claim's value. - * @return this same Builder instance. - * @throws IllegalArgumentException if the name is null - */ - public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { - assertNonNull(name); - addClaim(name, items); - return this; - } - /** * Add a custom Map Claim with the given items. *

@@ -381,7 +342,8 @@ public Builder withClaim(String name, Map map) throws IllegalArgument assertNonNull(name); // validate map contents if (map != null && !validateClaim(map)) { - throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Expected map containing Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } addClaim(name, map); return this; @@ -405,12 +367,55 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept assertNonNull(name); // validate list contents if (list != null && !validateClaim(list)) { - throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Expected list containing Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } addClaim(name, list); return this; } + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, String[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null. + */ + public Builder withArrayClaim(String name, Integer[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + + /** + * Add a custom Array Claim with the given items. + * + * @param name the Claim's name. + * @param items the Claim's value. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null + */ + public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, items); + return this; + } + /** * Add specific Claims to set as the Payload. If the provided map is null then * nothing is changed. @@ -426,8 +431,9 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept *

* * @param payloadClaims the values to use as Claims in the token's payload. - * @throws IllegalArgumentException if any of the claim keys or null, or if the values are not of a supported type. * @return this same Builder instance. + * @throws IllegalArgumentException if any of the claim keys or null, + * or if the values are not of a supported type. */ public Builder withPayload(Map payloadClaims) throws IllegalArgumentException { if (payloadClaims == null) { @@ -435,7 +441,8 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE } if (!validatePayload(payloadClaims)) { - throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " + + "Long, Double, String and Date"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder @@ -504,16 +511,18 @@ private static boolean isBasicType(Object value) { if (c.isArray()) { return c == Integer[].class || c == Long[].class || c == String[].class; } - return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Instant.class || c == Boolean.class; + return c == String.class || c == Integer.class || c == Long.class || c == Double.class + || c == Date.class || c == Instant.class || c == Boolean.class; } /** - * Creates a new JWT and signs is with the given algorithm + * Creates a new JWT and signs is with the given algorithm. * * @param algorithm used to sign the JWT * @return a new JWT token * @throws IllegalArgumentException if the provided algorithm is null. - * @throws JWTCreationException if the claims could not be converted to a valid JSON or there was a problem with the signing key. + * @throws JWTCreationException if the claims could not be converted to a valid JSON + * or there was a problem with the signing key. */ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException { if (algorithm == null) { @@ -546,10 +555,13 @@ private void addClaim(String name, Object value) { } private String sign() throws SignatureGenerationException { - String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); - String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); + String header = Base64.getUrlEncoder().withoutPadding() + .encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); + String payload = Base64.getUrlEncoder().withoutPadding() + .encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); - byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), + payload.getBytes(StandardCharsets.UTF_8)); String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes)); return String.format("%s.%s.%s", header, payload, signature); diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index 55855241..cc283095 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -42,7 +42,7 @@ final class JWTDecoder implements DecodedJWT, Serializable { payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); } catch (NullPointerException e) { throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { throw new JWTDecodeException("The input is not a valid base 64 encoded string.", e); } header = converter.parseHeader(headerJson); diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 5bcc7bc7..77dfef4e 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -16,11 +16,13 @@ import java.util.*; /** - * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also its signature matches. + * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, + * but also its signature matches. *

* This class is thread-safe. + * + * @see com.auth0.jwt.interfaces.JWTVerifier */ -@SuppressWarnings("WeakerAccess") public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; final Map claims; @@ -48,6 +50,9 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { return new BaseVerification(algorithm); } + /** + * {@link Verification} implementation that accepts all the expected Claim values for verification. + */ public static class BaseVerification implements Verification { private final Algorithm algorithm; private final Map claims; @@ -180,7 +185,8 @@ public Verification withClaim(String name, Date value) throws IllegalArgumentExc @Override public Verification withClaim(String name, Instant value) throws IllegalArgumentException { assertNonNull(name); - // Since date-time claims are serialized as epoch seconds, we need to compare them with only seconds-granularity + // Since date-time claims are serialized as epoch seconds, + // we need to compare them with only seconds-granularity requireClaim(name, value != null ? value.truncatedTo(ChronoUnit.SECONDS) : null); return this; } @@ -280,7 +286,8 @@ private static boolean isNullOrEmpty(String[] args) { * * @param token to verify. * @return a verified and decoded JWT. - * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to the one defined in the {@link JWTVerifier}. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to + * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. * @throws InvalidClaimException if a claim contained a different value than the expected one. @@ -296,7 +303,8 @@ public DecodedJWT verify(String token) throws JWTVerificationException { * * @param jwt to verify. * @return a verified and decoded JWT. - * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to the one defined in the {@link JWTVerifier}. + * @throws AlgorithmMismatchException if the algorithm stated in the token's header is not equal to + * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. * @throws InvalidClaimException if a claim contained a different value than the expected one. @@ -311,11 +319,13 @@ public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException { if (!expectedAlgorithm.getName().equals(jwt.getAlgorithm())) { - throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header."); + throw new AlgorithmMismatchException( + "The provided Algorithm doesn't match the one defined in the JWT's Header."); } } - private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { + private void verifyClaims(DecodedJWT jwt, Map claims) + throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { if (entry.getValue() instanceof NonEmptyClaim) { assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); @@ -327,8 +337,9 @@ private void verifyClaims(DecodedJWT jwt, Map claims) throws Tok private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { switch (expectedClaim.getKey()) { - // We use custom keys for audience in the expected claims to differentiate between validating that the audience - // contains all expected values, or validating that the audience contains at least one of the expected values. + // We use custom keys for audience in the expected claims to differentiate between + // validating that the audience contains all expected values, or validating that the audience contains + // at least one of the expected values. case AUDIENCE_EXACT: assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); break; @@ -354,7 +365,8 @@ private void verifyClaimValues(DecodedJWT jwt, Map.Entry expecte assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); break; default: - assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), expectedClaim.getValue()); + assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), + expectedClaim.getValue()); break; } } @@ -402,13 +414,15 @@ private void assertValidClaim(Claim claim, String claimName, Object value) { } if (!isValid) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } } private void assertValidStringClaim(String claimName, String value, String expectedValue) { if (!expectedValue.equals(value)) { - throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName)); + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } } @@ -434,8 +448,8 @@ private void assertInstantIsPast(Instant claimVal, long leeway, Instant now) { } private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) || - (!shouldContainAll && Collections.disjoint(audience, values))) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))) { throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } } @@ -452,7 +466,8 @@ private void assertValidIssuerClaim(String issuer, List value) { private static class NonEmptyClaim { private static NonEmptyClaim nonEmptyClaim; - private NonEmptyClaim() {} + private NonEmptyClaim() { + } public static NonEmptyClaim getInstance() { if (nonEmptyClaim == null) { diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index cb6cff3e..0a76028b 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -18,7 +18,8 @@ static String[] splitToken(String token) throws JWTDecodeException { parts = new String[]{parts[0], parts[1], ""}; } if (parts.length != 3) { - throw new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", parts.length)); + throw new JWTDecodeException( + String.format("The token was expected to have 3 parts, but got %s.", parts.length)); } return parts; } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index ff10a9aa..a5247005 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -139,47 +139,47 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { } /** - * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". + * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * - * @param secret the secret to use in the verify or signing instance. - * @return a valid HMAC384 Algorithm. + * @param secret the secret bytes to use in the verify or signing instance. + * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC384(String secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS384", "HmacSHA384", secret); + public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS256", "HmacSHA256", secret); } /** - * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". + * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret to use in the verify or signing instance. - * @return a valid HMAC512 Algorithm. + * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC512(String secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS512", "HmacSHA512", secret); + public static Algorithm HMAC384(String secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS384", "HmacSHA384", secret); } /** - * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". + * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret bytes to use in the verify or signing instance. - * @return a valid HMAC256 Algorithm. + * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS256", "HmacSHA256", secret); + public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS384", "HmacSHA384", secret); } /** - * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". + * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * - * @param secret the secret bytes to use in the verify or signing instance. - * @return a valid HMAC384 Algorithm. + * @param secret the secret to use in the verify or signing instance. + * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ - public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { - return new HMACAlgorithm("HS384", "HmacSHA384", secret); + public static Algorithm HMAC512(String secret) throws IllegalArgumentException { + return new HMACAlgorithm("HS512", "HmacSHA512", secret); } /** @@ -192,8 +192,7 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException { return new HMACAlgorithm("HS512", "HmacSHA512", secret); } - - + /** * Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256". @@ -314,7 +313,8 @@ protected Algorithm(String name, String description) { } /** - * Getter for the Id of the Private Key used to sign the tokens. This is usually specified as the `kid` claim in the Header. + * Getter for the Id of the Private Key used to sign the tokens. + * This is usually specified as the `kid` claim in the Header. * * @return the Key Id that identifies the Signing Key or null if it's not specified. */ @@ -332,7 +332,8 @@ public String getName() { } /** - * Getter for the description of this Algorithm, required when instantiating a Mac or Signature object. i.e. "HmacSHA256" + * Getter for the description of this Algorithm, + * required when instantiating a Mac or Signature object. i.e. "HmacSHA256" * * @return the algorithm description. */ @@ -349,15 +350,19 @@ public String toString() { * Verify the given token using this Algorithm instance. * * @param jwt the already decoded JWT that it's going to be verified. - * @throws SignatureVerificationException if the Token's Signature is invalid, meaning that it doesn't match the signatureBytes, or if the Key is invalid. + * @throws SignatureVerificationException if the Token's Signature is invalid, + * meaning that it doesn't match the signatureBytes, + * or if the Key is invalid. */ public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException; /** * Sign the given content using this Algorithm instance. * - * @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature. - * @param payloadBytes an array of bytes representing the base64 encoded payload content to be verified against the signature. + * @param headerBytes an array of bytes representing the base64 encoded header content + * to be verified against the signature. + * @param payloadBytes an array of bytes representing the base64 encoded payload content + * to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. */ @@ -376,7 +381,8 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene * Sign the given content using this Algorithm instance. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature. + * @param contentBytes an array of bytes representing the base64 encoded content + * to be verified against the signature. * @return the signature in a base64 encoded array of bytes * @throws SignatureGenerationException if the Key is invalid. */ diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java index 26a87bc4..7b8c5c2a 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java @@ -12,93 +12,98 @@ */ class CryptoHelper { - private static final byte JWT_PART_SEPARATOR = (byte)46; + private static final byte JWT_PART_SEPARATOR = (byte) 46; /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param header JWT header. - * @param payload JWT payload. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param header JWT header. + * @param payload JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return verifySignatureFor(algorithm, secretBytes, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + boolean verifySignatureFor( + String algorithm, + byte[] secretBytes, + String header, + String payload, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + return verifySignatureFor(algorithm, secretBytes, + header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); } /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), signatureBytes); - } - - /** - * Create signature for JWT header and payload. - * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. - * @return the signature bytes. - * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - */ - - byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException { - final Mac mac = Mac.getInstance(algorithm); - mac.init(new SecretKeySpec(secretBytes, algorithm)); - mac.update(headerBytes); - mac.update(JWT_PART_SEPARATOR); - return mac.doFinal(payloadBytes); + boolean verifySignatureFor( + String algorithm, + byte[] secretBytes, + byte[] headerBytes, + byte[] payloadBytes, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), + signatureBytes); } /** * Verify signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param publicKey algorithm public key. - * @param header JWT header. - * @param payload JWT payload. + * @param algorithm algorithm name. + * @param publicKey algorithm public key. + * @param header JWT header. + * @param payload JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes); + boolean verifySignatureFor( + String algorithm, + PublicKey publicKey, + String header, + String payload, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), + payload.getBytes(StandardCharsets.UTF_8), signatureBytes); } /** * Verify signature for JWT header and payload using a public key. * - * @param algorithm algorithm name. - * @param publicKey the public key to use for verification. - * @param headerBytes JWT header. - * @param payloadBytes JWT payload. + * @param algorithm algorithm name. + * @param publicKey the public key to use for verification. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. * @param signatureBytes JWT signature. * @return true if signature is valid. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + boolean verifySignatureFor( + String algorithm, + PublicKey publicKey, + byte[] headerBytes, + byte[] payloadBytes, + byte[] signatureBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initVerify(publicKey); s.update(headerBytes); @@ -110,17 +115,22 @@ boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerB /** * Create signature for JWT header and payload using a private key. * - * @param algorithm algorithm name. - * @param privateKey the private key to use for signing. - * @param headerBytes JWT header. + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. + * @param headerBytes JWT header. * @param payloadBytes JWT payload. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly + * or if this signature algorithm is unable to process the input data provided. */ - - byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] createSignatureFor( + String algorithm, + PrivateKey privateKey, + byte[] headerBytes, + byte[] payloadBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initSign(privateKey); s.update(headerBytes); @@ -130,75 +140,66 @@ byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] header } /** - * Verify signature. - * For valid verification, ensure the content is in the format {HEADER}.{PAYLOAD} + * Create signature for JWT header and payload. * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. - * @param contentBytes the content to which the signature applies. - * @param signatureBytes JWT signature. - * @return true if signature is valid. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. + * @param headerBytes JWT header. + * @param payloadBytes JWT payload. + * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException { - return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes); + byte[] createSignatureFor( + String algorithm, + byte[] secretBytes, + byte[] headerBytes, + byte[] payloadBytes + ) throws NoSuchAlgorithmException, InvalidKeyException { + final Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(secretBytes, algorithm)); + mac.update(headerBytes); + mac.update(JWT_PART_SEPARATOR); + return mac.doFinal(payloadBytes); } /** * Create signature. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param algorithm algorithm name. - * @param secretBytes algorithm secret. + * @param algorithm algorithm name. + * @param secretBytes algorithm secret. * @param contentBytes the content to be signed. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. */ - - byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException { + byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) + throws NoSuchAlgorithmException, InvalidKeyException { final Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(secretBytes, algorithm)); return mac.doFinal(contentBytes); } - /** - * Verify signature using a public key. - * For valid verification, ensure the content is in the format {HEADER}.{PAYLOAD} - * - * @param algorithm algorithm name. - * @param publicKey algorithm public key. - * @param contentBytes the content to which the signature applies. - * @param signatureBytes JWT signature. - * @return the signature bytes. - * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. - */ - - boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - final Signature s = Signature.getInstance(algorithm); - s.initVerify(publicKey); - s.update(contentBytes); - return s.verify(signatureBytes); - } - /** * Create signature using a private key. * To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD} * - * @param algorithm algorithm name. - * @param privateKey the private key to use for signing. + * @param algorithm algorithm name. + * @param privateKey the private key to use for signing. * @param contentBytes the content to be signed. * @return the signature bytes. * @throws NoSuchAlgorithmException if the algorithm is not supported. - * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. - * @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided. + * @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm. + * @throws SignatureException if this signature object is not initialized properly + * or if this signature algorithm is unable to process the input data provided. */ - byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] createSignatureFor( + String algorithm, + PrivateKey privateKey, + byte[] contentBytes + ) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { final Signature s = Signature.getInstance(algorithm); s.initSign(privateKey); s.update(contentBytes); 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 51fa70fa..26700a2a 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -24,7 +24,8 @@ class ECDSAAlgorithm extends Algorithm { private final int ecNumberSize; //Visible for testing - ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) + throws IllegalArgumentException { super(id, algorithm); if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); @@ -34,7 +35,8 @@ class ECDSAAlgorithm extends Algorithm { this.ecNumberSize = ecNumberSize; } - ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException { + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) + throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider); } @@ -46,12 +48,14 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), + jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException | IllegalArgumentException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException + | IllegalStateException | IllegalArgumentException e) { throw new SignatureVerificationException(this, e); } } @@ -116,25 +120,27 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { offset++; //Obtain R number length (Includes padding) and skip it - int rLength = derSignature[offset++]; - if (rLength > ecNumberSize + 1) { + int rlength = derSignature[offset++]; + if (rlength > ecNumberSize + 1) { throw new SignatureException("Invalid DER signature format."); } - int rPadding = ecNumberSize - rLength; + 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)); + 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; + offset += rlength + 1; //Obtain S number length. (Includes padding) - int sLength = derSignature[offset++]; - if (sLength > ecNumberSize + 1) { + int slength = derSignature[offset++]; + if (slength > ecNumberSize + 1) { throw new SignatureException("Invalid DER signature format."); } - int sPadding = ecNumberSize - sLength; + 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)); + System.arraycopy(derSignature, offset + Math.max(-spadding, 0), joseSignature, + ecNumberSize + Math.max(spadding, 0), slength + Math.min(spadding, 0)); return joseSignature; } @@ -146,12 +152,12 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { } // Retrieve R and S number's length and padding. - int rPadding = countPadding(joseSignature, 0, ecNumberSize); - int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); - int rLength = ecNumberSize - rPadding; - int sLength = ecNumberSize - sPadding; + int rpadding = countPadding(joseSignature, 0, ecNumberSize); + int spadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); + int rlength = ecNumberSize - rpadding; + int slength = ecNumberSize - spadding; - int length = 2 + rLength + 2 + sLength; + int length = 2 + rlength + 2 + slength; if (length > 255) { throw new SignatureException("Invalid JOSE signature format."); } @@ -174,31 +180,32 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { // Header with "min R" number length derSignature[offset++] = (byte) 0x02; - derSignature[offset++] = (byte) rLength; + derSignature[offset++] = (byte) rlength; // R number - if (rPadding < 0) { + 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); + int copyLength = Math.min(ecNumberSize, rlength); + System.arraycopy(joseSignature, rpadding, derSignature, offset, copyLength); offset += copyLength; } // Header with "min S" number length derSignature[offset++] = (byte) 0x02; - derSignature[offset++] = (byte) sLength; + derSignature[offset++] = (byte) slength; // S number - if (sPadding < 0) { + 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)); + System.arraycopy(joseSignature, ecNumberSize + spadding, derSignature, offset, + Math.min(ecNumberSize, slength)); } return derSignature; diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java index 26ad6a87..0306e7c4 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java @@ -21,7 +21,8 @@ class HMACAlgorithm extends Algorithm { private final byte[] secret; //Visible for testing - HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) throws IllegalArgumentException { + HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) + throws IllegalArgumentException { super(id, algorithm); if (secretBytes == null) { throw new IllegalArgumentException("The Secret cannot be null"); @@ -50,7 +51,8 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException { public void verify(DecodedJWT jwt) throws SignatureVerificationException { try { byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature()); - boolean valid = crypto.verifySignatureFor(getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); + boolean valid = crypto.verifySignatureFor( + getDescription(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java index ade07b0e..5c6c0fc5 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java @@ -3,7 +3,6 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; - import java.util.Base64; class NoneAlgorithm extends Algorithm { diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index cd1e97a5..0c7a5b57 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -24,7 +24,8 @@ class RSAAlgorithm extends Algorithm { private final CryptoHelper crypto; //Visible for testing - RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException { + RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider) + throws IllegalArgumentException { super(id, algorithm); if (keyProvider == null) { throw new IllegalArgumentException("The Key Provider cannot be null."); @@ -45,11 +46,13 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes); + boolean valid = crypto.verifySignatureFor( + getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes); if (!valid) { throw new SignatureVerificationException(this); } - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalArgumentException | IllegalStateException e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException + | IllegalArgumentException | IllegalStateException e) { throw new SignatureVerificationException(this, e); } } @@ -66,7 +69,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene throw new SignatureGenerationException(this, e); } } - + @Override public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { try { diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java b/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java index 1d7ba1ce..d6b71205 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/AlgorithmMismatchException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that will be thrown if the exception doesn't match the one mentioned in the JWT Header. + */ public class AlgorithmMismatchException extends JWTVerificationException { public AlgorithmMismatchException(String message) { super(message); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java index ab348323..f80fb752 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/InvalidClaimException.java @@ -1,6 +1,8 @@ package com.auth0.jwt.exceptions; - +/** + * The exception that will be thrown while verifying Claims of a JWT. + */ public class InvalidClaimException extends JWTVerificationException { public InvalidClaimException(String message) { super(message); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java index 5bf4facb..c7e162ea 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTCreationException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown when a JWT cannot be created. + */ public class JWTCreationException extends RuntimeException { public JWTCreationException(String message, Throwable cause) { super(message, cause); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java index 93799d31..448714e9 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTDecodeException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown when any part of the token contained an invalid JWT or JSON format. + */ public class JWTDecodeException extends JWTVerificationException { public JWTDecodeException(String message) { this(message, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java index 2ccfcccf..dd36dcd3 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/JWTVerificationException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * Parent to all the exception thrown while verifying a JWT. + */ public class JWTVerificationException extends RuntimeException { public JWTVerificationException(String message) { this(message, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java index 3637f97a..4b7668a0 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureGenerationException.java @@ -2,6 +2,9 @@ import com.auth0.jwt.algorithms.Algorithm; +/** + * The exception that is thrown when signature is not able to be generated. + */ public class SignatureGenerationException extends JWTCreationException { public SignatureGenerationException(Algorithm algorithm, Throwable cause) { super("The Token's Signature couldn't be generated when signing using the Algorithm: " + algorithm, cause); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java index 12bd3429..fa7c3cab 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/SignatureVerificationException.java @@ -2,6 +2,9 @@ import com.auth0.jwt.algorithms.Algorithm; +/** + * The exception that is thrown if the Signature verification fails. + */ public class SignatureVerificationException extends JWTVerificationException { public SignatureVerificationException(Algorithm algorithm) { this(algorithm, null); diff --git a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java index f5d2e67a..66f28285 100644 --- a/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java +++ b/lib/src/main/java/com/auth0/jwt/exceptions/TokenExpiredException.java @@ -1,5 +1,8 @@ package com.auth0.jwt.exceptions; +/** + * The exception that is thrown if the token is expired. + */ public class TokenExpiredException extends JWTVerificationException { private static final long serialVersionUID = -7076928975713577708L; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 0a782268..3746dcd2 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -24,8 +24,15 @@ class BasicHeader implements Header, Serializable { private final String keyId; private final Map tree; private final ObjectReader objectReader; - - BasicHeader(String algorithm, String type, String contentType, String keyId, Map tree, ObjectReader objectReader) { + + BasicHeader( + String algorithm, + String type, + String contentType, + String keyId, + Map tree, + ObjectReader objectReader + ) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; diff --git a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java index 4db20119..b1f8e6d3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java @@ -32,12 +32,12 @@ public void serialize(T holder, JsonGenerator gen, SerializerProvider provider) /** * Writes the given entry to the JSON representation. Custom claim serialization handling can override this method - * to provide use-case specific serialization. Implementors who override this method must write the field name and the - * field value. + * to provide use-case specific serialization. Implementors who override this method must write + * the field name and the field value. * * @param entry The entry that corresponds to the JSON field to write * @param gen The {@code JsonGenerator} to use - * @throws IOException + * @throws IOException if there is either an underlying I/O problem or encoding issue at format layer */ protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { gen.writeFieldName(entry.getKey()); diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 6bba2caa..8a6cbcf8 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -13,10 +13,10 @@ /** * Jackson deserializer implementation for converting from JWT Header parts. - * - * @see JWTParser *

* This class is thread-safe. + * + * @see JWTParser */ class HeaderDeserializer extends StdDeserializer { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index 84a8b867..fe1600bd 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -9,9 +9,12 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; - import java.io.IOException; +/** + * This class helps in decoding the Header and Payload of the JWT using + * {@link HeaderSerializer} and {@link PayloadSerializer}. + */ public class JWTParser implements JWTPartsParser { private final ObjectReader payloadReader; private final ObjectReader headerReader; 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 0d816128..54a09575 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -74,24 +74,24 @@ public Instant asInstant() { @Override @SuppressWarnings("unchecked") - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { if (!data.isArray()) { return null; } - T[] arr = (T[]) Array.newInstance(tClazz, data.size()); + T[] arr = (T[]) Array.newInstance(clazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = objectReader.treeToValue(data.get(i), tClazz); + arr[i] = objectReader.treeToValue(data.get(i), clazz); } catch (JsonProcessingException e) { - throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } } return arr; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { if (!data.isArray()) { return null; } @@ -99,9 +99,9 @@ public List asList(Class tClazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(objectReader.treeToValue(data.get(i), tClazz)); + list.add(objectReader.treeToValue(data.get(i), clazz)); } catch (JsonProcessingException e) { - throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } } return list; @@ -124,11 +124,11 @@ public Map asMap() throws JWTDecodeException { } @Override - public T as(Class tClazz) throws JWTDecodeException { + public T as(Class clazz) throws JWTDecodeException { try { - return objectReader.treeAsTokens(data).readValueAs(tClazz); + return objectReader.treeAsTokens(data).readValueAs(clazz); } catch (IOException e) { - throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); + throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java index 664328de..2975b51b 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java @@ -53,12 +53,12 @@ public Instant asInstant() { } @Override - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { return null; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { return null; } @@ -68,7 +68,7 @@ public Map asMap() throws JWTDecodeException { } @Override - public T as(Class tClazz) throws JWTDecodeException { + public T as(Class clazz) throws JWTDecodeException { return null; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 46bd7ebe..09009267 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -16,10 +16,10 @@ /** * Jackson deserializer implementation for converting from JWT Payload parts. - * - * @see JWTParser *

* This class is thread-safe. + * + * @see JWTParser */ class PayloadDeserializer extends StdDeserializer { @@ -80,7 +80,8 @@ Instant getInstantFromSeconds(Map tree, String claimName) { return null; } if (!node.canConvertToLong()) { - throw new JWTDecodeException(String.format("The claim '%s' contained a non-numeric date value.", claimName)); + throw new JWTDecodeException( + String.format("The claim '%s' contained a non-numeric date value.", claimName)); } return Instant.ofEpochSecond(node.asLong()); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index a446741c..75e79474 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -13,10 +13,10 @@ /** * Decoder of string JSON Web Tokens into their POJO representations. - * - * @see Payload *

* This class is thread-safe. + * + * @see Payload */ class PayloadImpl implements Payload, Serializable { @@ -32,7 +32,17 @@ class PayloadImpl implements Payload, Serializable { private final Map tree; private final ObjectReader objectReader; - PayloadImpl(String issuer, String subject, List audience, Instant expiresAt, Instant notBefore, Instant issuedAt, String jwtId, Map tree, ObjectReader objectReader) { + PayloadImpl( + String issuer, + String subject, + List audience, + Instant expiresAt, + Instant notBefore, + Instant issuedAt, + String jwtId, + Map tree, + ObjectReader objectReader + ) { this.issuer = issuer; this.subject = subject; this.audience = audience != null ? Collections.unmodifiableList(audience) : null; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 497a3a94..dc138c08 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -10,10 +10,10 @@ /** * Jackson serializer implementation for converting into JWT Payload parts. - * - * @see com.auth0.jwt.JWTCreator *

* This class is thread-safe. + * + * @see com.auth0.jwt.JWTCreator */ public class PayloadSerializer extends ClaimsSerializer { public PayloadSerializer() { @@ -47,7 +47,7 @@ private void writeAudience(JsonGenerator gen, Map.Entry e) throw List audList = (List) e.getValue(); for (Object aud : audList) { if (aud instanceof String) { - audArray.add((String)aud); + audArray.add((String) aud); } } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java index cc2b3db8..ea166b0d 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java @@ -1,6 +1,8 @@ package com.auth0.jwt.impl; - +/** + * Contains the claim name for all Public claims. + */ public interface PublicClaims { //Header diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index cc5256f4..fdfac715 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -81,23 +81,24 @@ default Instant asInstant() { /** * Get this Claim as an Array of type T. * If the value isn't an Array, null will be returned. + * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as an Array or null. * @throws JWTDecodeException if the values inside the Array can't be converted to a class T. */ - T[] asArray(Class tClazz) throws JWTDecodeException; + T[] asArray(Class clazz) throws JWTDecodeException; /** * Get this Claim as a List of type T. * If the value isn't an Array, null will be returned. * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as a List or null. * @throws JWTDecodeException if the values inside the List can't be converted to a class T. */ - List asList(Class tClazz) throws JWTDecodeException; + List asList(Class clazz) throws JWTDecodeException; /** * Get this Claim as a generic Map of values. @@ -111,9 +112,9 @@ default Instant asInstant() { * Get this Claim as a custom type T. * * @param type - * @param tClazz the type class + * @param clazz the type class * @return the value as instance of T. * @throws JWTDecodeException if the value can't be converted to a class T. */ - T as(Class tClazz) throws JWTDecodeException; + T as(Class clazz) throws JWTDecodeException; } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java index 520e35c8..e1c6efcc 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java @@ -3,7 +3,8 @@ import com.auth0.jwt.exceptions.JWTDecodeException; /** - * The JWTPartsParser class defines which parts of the JWT should be converted to it's specific Object representation instance. + * The JWTPartsParser class defines which parts of the JWT should be converted + * to it's specific Object representation instance. */ public interface JWTPartsParser { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index a335a83e..3555878e 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -3,10 +3,13 @@ import com.auth0.jwt.exceptions.JWTVerificationException; +/** + * Used to verify the JWT for it's signature and claims. + */ public interface JWTVerifier { /** - * Performs the verification against the given Token + * Performs the verification against the given Token. * * @param token to verify. * @return a verified and decoded JWT. @@ -15,7 +18,7 @@ public interface JWTVerifier { DecodedJWT verify(String token) throws JWTVerificationException; /** - * Performs the verification against the given decoded JWT + * Performs the verification against the given decoded JWT. * * @param jwt to verify. * @return a verified and decoded JWT. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index fa3b13c9..fd8baea5 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -14,7 +14,8 @@ interface KeyProvider { /** * Getter for the Public Key instance with the given Id. Used to verify the signature on the JWT verification stage. * - * @param keyId the Key Id specified in the Token's Header or null if none is available. Provides a hint on which Public Key to use to verify the token's signature. + * @param keyId the Key Id specified in the Token's Header or null if none is available. + * Provides a hint on which Public Key to use to verify the token's signature. * @return the Public Key instance */ U getPublicKeyById(String keyId); @@ -27,7 +28,8 @@ interface KeyProvider { R getPrivateKey(); /** - * Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header. + * Getter for the Id of the Private Key used to sign the tokens. + * This represents the `kid` claim and will be placed in the Header. * * @return the Key Id that identifies the Private Key or null if it's not specified. */ diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 1fb6aece..071b3e9c 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -60,7 +60,7 @@ default Verification withIssuer(String issuer) { * will have to provide their own implementation. * * The default implementation throws an {@linkplain UnsupportedOperationException}. - * + * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. */ @@ -69,8 +69,8 @@ default Verification withAnyOfAudience(String... audience) { } /** - * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. - * Setting a specific leeway value on a given Claim will override this value for that Claim. + * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims + * will still be valid. Setting a specific leeway value on a given Claim will override this value for that Claim. * * @param leeway the window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid. * @return this same Verification instance. @@ -80,7 +80,8 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid. - * Expiration Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * Expiration Date is always verified when the value is present. + * This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Expires At Claim will still be valid. * @return this same Verification instance. @@ -90,7 +91,8 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid. - * Not Before Date is always verified when the value is present. This method overrides the value set with acceptLeeway + * Not Before Date is always verified when the value is present. + * This method overrides the value set with acceptLeeway * * @param leeway the window in seconds in which the Not Before Claim will still be valid. * @return this same Verification instance. @@ -101,8 +103,10 @@ default Verification withAnyOfAudience(String... audience) { /** * Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid. * This method overrides the value set with {@link #acceptLeeway(long)}. - * By default, the Issued At claim is always verified when the value is present, unless disabled with {@link #ignoreIssuedAt()}. - * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, and this method has no effect. + * By default, the Issued At claim is always verified when the value is present, + * unless disabled with {@link #ignoreIssuedAt()}. + * If Issued At verification has been disabled, no verification of the Issued At claim will be performed, + * and this method has no effect. * * @param leeway the window in seconds in which the Issued At Claim will still be valid. * @return this same Verification instance. @@ -120,6 +124,7 @@ default Verification withAnyOfAudience(String... audience) { /** * Require a claim to be present, with any value. + * * @param name the Claim's name. * @return this same Verification instance * @throws IllegalArgumentException if the name is null. @@ -177,8 +182,8 @@ default Verification withAnyOfAudience(String... audience) { Verification withClaim(String name, String value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; when verifying - * date-time claim value, any time units more granular than seconds will not be considered. + * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * when verifying date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. @@ -188,8 +193,8 @@ default Verification withAnyOfAudience(String... audience) { Verification withClaim(String name, Date value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; when verifying - * a date-time claim value, any time units more granular than seconds will not be considered. + * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * when verifying a date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java index 120f6a9d..77d573bc 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java @@ -71,12 +71,12 @@ public Date asDate() { } @Override - public T[] asArray(Class tClazz) throws JWTDecodeException { + public T[] asArray(Class clazz) throws JWTDecodeException { return null; } @Override - public List asList(Class tClazz) throws JWTDecodeException { + public List asList(Class clazz) throws JWTDecodeException { return null; } From 0029a6363f89c1af43db64a76a002e8169c2656c Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 25 Mar 2022 16:25:50 +0530 Subject: [PATCH 237/355] [SDK-3155] Predicate based Claim verification (#562) * Rename claims -> expectedClaims in verifier for clarity * added method to validate claim value using predicate * Using Predicates for Verification (#560) * Use predicates for verification * Code review changes * Add additional tests for predicate based verification * Fixed Lint issues --- .../main/java/com/auth0/jwt/JWTVerifier.java | 366 ++++++++---------- .../auth0/jwt/interfaces/Verification.java | 12 + .../java/com/auth0/jwt/JWTVerifierTest.java | 207 +++++----- .../jwt/interfaces/VerificationTest.java | 6 + 4 files changed, 283 insertions(+), 308 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 77dfef4e..8b14ff86 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -14,6 +14,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.function.BiPredicate; /** * The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, @@ -25,17 +26,12 @@ */ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; - final Map claims; - private final Clock clock; + final Map> expectedChecks; private final JWTParser parser; - static final String AUDIENCE_EXACT = "AUDIENCE_EXACT"; - static final String AUDIENCE_CONTAINS = "AUDIENCE_CONTAINS"; - - JWTVerifier(Algorithm algorithm, Map claims, Clock clock) { + JWTVerifier(Algorithm algorithm, Map> expectedChecks) { this.algorithm = algorithm; - this.claims = Collections.unmodifiableMap(claims); - this.clock = clock; + this.expectedChecks = Collections.unmodifiableMap(expectedChecks); this.parser = new JWTParser(); } @@ -55,9 +51,11 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { */ public static class BaseVerification implements Verification { private final Algorithm algorithm; - private final Map claims; + private final Map> expectedChecks; private long defaultLeeway; + private final Map customLeeways; private boolean ignoreIssuedAt; + private Clock clock; BaseVerification(Algorithm algorithm) throws IllegalArgumentException { if (algorithm == null) { @@ -65,33 +63,42 @@ public static class BaseVerification implements Verification { } this.algorithm = algorithm; - this.claims = new HashMap<>(); + this.expectedChecks = new LinkedHashMap<>(); + this.customLeeways = new HashMap<>(); this.defaultLeeway = 0; } @Override public Verification withIssuer(String... issuer) { - requireClaim(PublicClaims.ISSUER, isNullOrEmpty(issuer) ? null : Arrays.asList(issuer)); + List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); + checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { + if (value == null || !value.contains(claim.asString())) { + throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); + } + return true; + })); return this; } @Override public Verification withSubject(String subject) { - requireClaim(PublicClaims.SUBJECT, subject); + checkIfNeedToRemove(PublicClaims.SUBJECT, subject, (claim, decodedJWT) -> subject.equals(claim.asString())); return this; } @Override public Verification withAudience(String... audience) { - claims.remove(AUDIENCE_CONTAINS); - requireClaim(AUDIENCE_EXACT, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); + checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> + assertValidAudienceClaim(decodedJWT.getAudience(), value, true))); return this; } @Override public Verification withAnyOfAudience(String... audience) { - claims.remove(AUDIENCE_EXACT); - requireClaim(AUDIENCE_CONTAINS, isNullOrEmpty(audience) ? null : Arrays.asList(audience)); + List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); + checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> + assertValidAudienceClaim(decodedJWT.getAudience(), value, false))); return this; } @@ -105,21 +112,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.EXPIRES_AT, leeway); + customLeeways.put(PublicClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.NOT_BEFORE, leeway); + customLeeways.put(PublicClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - requireClaim(PublicClaims.ISSUED_AT, leeway); + customLeeways.put(PublicClaims.ISSUED_AT, leeway); return this; } @@ -131,49 +138,54 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - requireClaim(PublicClaims.JWT_ID, jwtId); + checkIfNeedToRemove(PublicClaims.JWT_ID, jwtId, ((claim, decodedJWT) -> jwtId.equals(claim.asString()))); return this; } @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, NonEmptyClaim.getInstance()); + withClaim(name, ((claim, decodedJWT) -> { + if (claim instanceof NullClaim) { + throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); + } + return true; + })); return this; } @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asBoolean()))); return this; } @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asInt()))); return this; } @Override public Verification withClaim(String name, Long value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asLong()))); return this; } @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asDouble()))); return this; } @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, value); + checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asString()))); return this; } @@ -187,28 +199,37 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument assertNonNull(name); // Since date-time claims are serialized as epoch seconds, // we need to compare them with only seconds-granularity - requireClaim(name, value != null ? value.truncatedTo(ChronoUnit.SECONDS) : null); + checkIfNeedToRemove(name, value, + ((claim, decodedJWT) -> value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); + return this; + } + + @Override + public Verification withClaim(String name, BiPredicate predicate) + throws IllegalArgumentException { + assertNonNull(name); + checkIfNeedToRemove(name, predicate, predicate); return this; } @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); - requireClaim(name, items); + checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); return this; } @@ -225,59 +246,122 @@ public JWTVerifier build() { * @return a new JWTVerifier instance with a custom {@link java.time.Clock} */ public JWTVerifier build(Clock clock) { - addLeewayToDateClaims(); - return new JWTVerifier(algorithm, claims, clock); + this.clock = clock; + addMandatoryClaimChecks(); + return new JWTVerifier(algorithm, expectedChecks); } - private void assertPositive(long leeway) { - if (leeway < 0) { - throw new IllegalArgumentException("Leeway value can't be negative."); + /** + * Fetches the Leeway set for claim or returns the {@link BaseVerification#defaultLeeway}. + * + * @param name Claim for which leeway is fetched + * @return Leeway value set for the claim + */ + public long getLeewayFor(String name) { + return customLeeways.getOrDefault(name, defaultLeeway); + } + + private void addMandatoryClaimChecks() { + long expiresAtLeeway = getLeewayFor(PublicClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); + + checkIfNeedToRemove(PublicClaims.EXPIRES_AT, expiresAtLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), expiresAtLeeway, true))); + checkIfNeedToRemove(PublicClaims.NOT_BEFORE, notBeforeLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), notBeforeLeeway, false))); + if (!ignoreIssuedAt) { + checkIfNeedToRemove(PublicClaims.ISSUED_AT, issuedAtLeeway, ((claim, decodedJWT) -> + assertValidInstantClaim(claim.asInstant(), issuedAtLeeway, false))); } } - private void assertNonNull(String name) { - if (name == null) { - throw new IllegalArgumentException("The Custom Claim's name can't be null."); + private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimValue) { + List claimArr; + Object[] claimAsObject = claim.as(Object[].class); + + // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. + if (expectedClaimValue instanceof Long[]) { + // convert Integers to Longs for comparison with equals + claimArr = new ArrayList<>(claimAsObject.length); + for (Object cao : claimAsObject) { + if (cao instanceof Integer) { + claimArr.add(((Integer) cao).longValue()); + } else { + claimArr.add(cao); + } + } + } else { + claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); } + List valueArr = Arrays.asList(expectedClaimValue); + return claimArr.containsAll(valueArr); } - private void addLeewayToDateClaims() { - if (!claims.containsKey(PublicClaims.EXPIRES_AT)) { - claims.put(PublicClaims.EXPIRES_AT, defaultLeeway); + private boolean assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { + Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); + if (shouldBeFuture) { + return assertInstantIsFuture(claimVal, leeway, now); + } else { + return assertInstantIsPast(claimVal, leeway, now); } - if (!claims.containsKey(PublicClaims.NOT_BEFORE)) { - claims.put(PublicClaims.NOT_BEFORE, defaultLeeway); + } + + private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { + if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); } - if (ignoreIssuedAt) { - claims.remove(PublicClaims.ISSUED_AT); - return; + return true; + } + + private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { + if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { + throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); + } + return true; + } + + private boolean assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + if (audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))) { + throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); } - if (!claims.containsKey(PublicClaims.ISSUED_AT)) { - claims.put(PublicClaims.ISSUED_AT, defaultLeeway); + return true; + } + + private void assertPositive(long leeway) { + if (leeway < 0) { + throw new IllegalArgumentException("Leeway value can't be negative."); + } + } + + private void assertNonNull(String name) { + if (name == null) { + throw new IllegalArgumentException("The Custom Claim's name can't be null."); } } - private void requireClaim(String name, Object value) { + private void checkIfNeedToRemove(String name, Object value, BiPredicate predicate) { if (value == null) { - claims.remove(name); + expectedChecks.remove(name); return; } - claims.put(name, value); + expectedChecks.put(name, predicate); } - } - private static boolean isNullOrEmpty(String[] args) { - if (args == null || args.length == 0) { - return true; - } - boolean isAllNull = true; - for (String arg : args) { - if (arg != null) { - isAllNull = false; - break; + private boolean isNullOrEmpty(String[] args) { + if (args == null || args.length == 0) { + return true; + } + boolean isAllNull = true; + for (String arg : args) { + if (arg != null) { + isAllNull = false; + break; + } } + return isAllNull; } - return isAllNull; } @@ -313,7 +397,7 @@ public DecodedJWT verify(String token) throws JWTVerificationException { public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { verifyAlgorithm(jwt, algorithm); algorithm.verify(jwt); - verifyClaims(jwt, claims); + verifyClaims(jwt, expectedChecks); return jwt; } @@ -324,156 +408,20 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws } } - private void verifyClaims(DecodedJWT jwt, Map claims) + private void verifyClaims(DecodedJWT jwt, Map> claims) throws TokenExpiredException, InvalidClaimException { - for (Map.Entry entry : claims.entrySet()) { - if (entry.getValue() instanceof NonEmptyClaim) { - assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); - } else { - verifyClaimValues(jwt, entry); - } - } - } - - private void verifyClaimValues(DecodedJWT jwt, Map.Entry expectedClaim) { - switch (expectedClaim.getKey()) { - // We use custom keys for audience in the expected claims to differentiate between - // validating that the audience contains all expected values, or validating that the audience contains - // at least one of the expected values. - case AUDIENCE_EXACT: - assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), true); - break; - case AUDIENCE_CONTAINS: - assertValidAudienceClaim(jwt.getAudience(), (List) expectedClaim.getValue(), false); - break; - case PublicClaims.EXPIRES_AT: - assertValidInstantClaim(jwt.getExpiresAtAsInstant(), (Long) expectedClaim.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidInstantClaim(jwt.getIssuedAtAsInstant(), (Long) expectedClaim.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidInstantClaim(jwt.getNotBeforeAsInstant(), (Long) expectedClaim.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) expectedClaim.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(expectedClaim.getKey(), jwt.getId(), (String) expectedClaim.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), - expectedClaim.getValue()); - break; - } - } - - private void assertClaimPresent(Claim claim, String claimName) { - if (claim instanceof NullClaim) { - throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName)); - } - } - - private void assertValidClaim(Claim claim, String claimName, Object value) { - boolean isValid = false; - if (value instanceof String) { - isValid = value.equals(claim.asString()); - } else if (value instanceof Integer) { - isValid = value.equals(claim.asInt()); - } else if (value instanceof Long) { - isValid = value.equals(claim.asLong()); - } else if (value instanceof Boolean) { - isValid = value.equals(claim.asBoolean()); - } else if (value instanceof Double) { - isValid = value.equals(claim.asDouble()); - } else if (value instanceof Instant) { - isValid = value.equals(claim.asInstant()); - } else if (value instanceof Object[]) { - List claimArr; - Object[] claimAsObject = claim.as(Object[].class); - - // Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. - if (value instanceof Long[]) { - // convert Integers to Longs for comparison with equals - claimArr = new ArrayList<>(claimAsObject.length); - for (Object cao : claimAsObject) { - if (cao instanceof Integer) { - claimArr.add(((Integer) cao).longValue()); - } else { - claimArr.add(cao); - } - } - } else { - claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); - } - List valueArr = Arrays.asList((Object[]) value); - isValid = claimArr.containsAll(valueArr); - } - - if (!isValid) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidStringClaim(String claimName, String value, String expectedValue) { - if (!expectedValue.equals(value)) { - throw new InvalidClaimException( - String.format("The Claim '%s' value doesn't match the required one.", claimName)); - } - } - - private void assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { - Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); - if (shouldBeFuture) { - assertInstantIsFuture(claimVal, leeway, now); - } else { - assertInstantIsPast(claimVal, leeway, now); - } - } - - private void assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { - throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); - } - } - - private void assertInstantIsPast(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { - throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); - } - } - - private void assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); - } - } + for (Map.Entry> entry : claims.entrySet()) { + boolean isValid; + String claimName = entry.getKey(); + BiPredicate expectedCheck = entry.getValue(); + Claim claim = jwt.getClaim(claimName); - private void assertValidIssuerClaim(String issuer, List value) { - if (issuer == null || !value.contains(issuer)) { - throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); - } - } - - /** - * Simple singleton used to mark that a claim should only be verified for presence. - */ - private static class NonEmptyClaim { - private static NonEmptyClaim nonEmptyClaim; - - private NonEmptyClaim() { - } + isValid = expectedCheck.test(claim, jwt); - public static NonEmptyClaim getInstance() { - if (nonEmptyClaim == null) { - nonEmptyClaim = new NonEmptyClaim(); + if (!isValid) { + throw new InvalidClaimException( + String.format("The Claim '%s' value doesn't match the required one.", claimName)); } - return nonEmptyClaim; } } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 071b3e9c..ffe0b920 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.util.Date; +import java.util.function.BiPredicate; /** * Holds the Claims and claim-based configurations required for a JWT to be considered valid. @@ -205,6 +206,17 @@ default Verification withClaim(String name, Instant value) throws IllegalArgumen return withClaim(name, value != null ? Date.from(value) : null); } + /** + * Executes the predicate provided during the verification + * and passes the verification if the predicate returns true. + * + * @param name the Claim's name + * @param predicate the predicate check to be done. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException; + /** * Require a specific Array Claim to contain at least the given items. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 93a7bcd1..1d80d802 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -4,6 +4,8 @@ import com.auth0.jwt.exceptions.AlgorithmMismatchException; import com.auth0.jwt.exceptions.InvalidClaimException; import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.impl.PublicClaims; +import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import org.junit.Rule; @@ -16,11 +18,11 @@ import java.time.ZoneId; import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.function.BiPredicate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.Mockito.mock; public class JWTVerifierTest { @@ -263,63 +265,29 @@ public void shouldRemoveAudienceWhenPassingNullReference() { .withAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); verifier = JWTVerifier.init(algorithm) .withAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); verifier = JWTVerifier.init(algorithm) .withAudience() .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_EXACT))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); String emptyAud = " "; verifier = JWTVerifier.init(algorithm) .withAudience(emptyAud) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_EXACT, Collections.singletonList(emptyAud))); - } - - @Test - public void shouldRemoveAudienceWhenPassingNullReferenceWithAnyOfAudience() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience((String) null) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience((String[]) null) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience() - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey(JWTVerifier.AUDIENCE_CONTAINS))); - - String emptyAud = " "; - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience(emptyAud) - .build(); - - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry(JWTVerifier.AUDIENCE_CONTAINS, Collections.singletonList(emptyAud))); + assertThat(verifier.expectedChecks, is(notNullValue())); } @Test @@ -330,16 +298,16 @@ public void shouldRemoveAudienceWhenPassingNull() { .withAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); verifier = JWTVerifier.init(algorithm) .withAudience("John") .withAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); } @Test @@ -350,16 +318,16 @@ public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() { .withAnyOfAudience((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); verifier = JWTVerifier.init(algorithm) .withAnyOfAudience("John") .withAnyOfAudience((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("aud"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("aud"))); } @Test @@ -453,10 +421,10 @@ public void shouldThrowOnInvalidCustomClaimValue() { exception.expect(InvalidClaimException.class); exception.expectMessage("The Claim 'name' value doesn't match the required one."); String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - Map map = new HashMap<>(); - map.put("name", new Object()); - JWTVerifier verifier = new JWTVerifier(Algorithm.HMAC256("secret"), map, Clock.systemUTC()); - verifier.verify(token); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "check") + .build() + .verify(token); } @Test @@ -533,8 +501,8 @@ public void shouldRemoveCustomClaimOfTypeDateWhenNull() { .withClaim("name", (Date) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); } @Test @@ -593,76 +561,76 @@ public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsIntegerAndLong() } // Generic Delta - @SuppressWarnings("RedundantCast") @Test public void shouldAddDefaultLeewayToDateClaims() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 0L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 0L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 0L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(0L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldAddCustomLeewayToDateClaims() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultIssuedAtLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptIssuedAt(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 9999L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultExpiresAtLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptExpiresAt(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 9999L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 1234L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); } - @SuppressWarnings("RedundantCast") @Test public void shouldOverrideDefaultNotBeforeLeeway() { Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm); + JWTVerifier verifier = verification .acceptLeeway(1234L) .acceptNotBefore(9999L) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iat", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("exp", (Object) 1234L)); - assertThat(verifier.claims, hasEntry("nbf", (Object) 9999L)); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(9999L)); } @Test @@ -860,16 +828,16 @@ public void shouldRemoveClaimWhenPassingNull() { .withIssuer((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer("iss") .withIssuer((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); } @Test @@ -879,30 +847,29 @@ public void shouldRemoveIssuerWhenPassingNullReference() { .withIssuer((String) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer((String[]) null) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); verifier = JWTVerifier.init(algorithm) .withIssuer() .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, not(hasKey("iss"))); + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("iss"))); String emptyIss = " "; verifier = JWTVerifier.init(algorithm) .withIssuer(emptyIss) .build(); - assertThat(verifier.claims, is(notNullValue())); - assertThat(verifier.claims, hasEntry("iss", Collections.singletonList(emptyIss))); + assertThat(verifier.expectedChecks, is(notNullValue())); } @Test @@ -1055,4 +1022,46 @@ public void shouldVerifyStandardClaimPresence() { DecodedJWT decodedJWT = verifier.verify(jwt); assertThat(decodedJWT, is(notNullValue())); } + + @Test + public void shouldSuccessfullyVerifyClaimWithPredicate() { + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "claimValue".equals(claim.asString())) + .withClaim(PublicClaims.ISSUED_AT, ((claim, decodedJWT) -> false)) + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldThrowWhenPredicateReturnsFalse() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .build() + .verify(jwt); + } + + @Test + public void shouldRemovePredicateCheckForNull() { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .withClaim("claimName", (BiPredicate) null) + .build(); + + assertThat(verifier.expectedChecks, is(notNullValue())); + assertThat(verifier.expectedChecks, not(hasKey("claimName"))); + } + } diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index e9f4f815..c7f89763 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -7,6 +7,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.function.BiPredicate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; @@ -149,6 +150,11 @@ public Verification withArrayClaim(String name, Long... items) throws IllegalArg return null; } + @Override + public Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException { + return null; + } + @Override public Verification ignoreIssuedAt() { return null; From e37301aad52681174bcd2dc39844ab56408e7b8a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 25 Mar 2022 16:38:56 +0530 Subject: [PATCH 238/355] [SDK-3158] Null claim handling (#564) * Handle claim difference between missing and null * Verification with null claims * JWT creation support for null values * Test cases for JWT verification and construction * Add JWT decode test cases * Fix broken tests * Fixed Lint issues * Fixed formatting errors * Add test case to check Claim toString conversion --- .../main/java/com/auth0/jwt/JWTCreator.java | 45 ++++-- .../main/java/com/auth0/jwt/JWTVerifier.java | 13 +- .../com/auth0/jwt/impl/JsonNodeClaim.java | 39 +++-- .../java/com/auth0/jwt/impl/NullClaim.java | 79 --------- .../java/com/auth0/jwt/interfaces/Claim.java | 10 ++ .../auth0/jwt/interfaces/Verification.java | 9 ++ .../java/com/auth0/jwt/JWTCreatorTest.java | 152 +++++++++--------- .../java/com/auth0/jwt/JWTDecoderTest.java | 21 ++- .../java/com/auth0/jwt/JWTVerifierTest.java | 43 +++++ .../com/auth0/jwt/impl/BasicHeaderTest.java | 3 +- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 45 +++++- .../com/auth0/jwt/impl/NullClaimTest.java | 82 ---------- .../com/auth0/jwt/impl/PayloadImplTest.java | 3 +- .../com/auth0/jwt/interfaces/ClaimTest.java | 5 + .../jwt/interfaces/VerificationTest.java | 5 + 15 files changed, 273 insertions(+), 281 deletions(-) delete mode 100644 lib/src/main/java/com/auth0/jwt/impl/NullClaim.java delete mode 100644 lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 6b355b36..e1159ae4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -75,7 +75,6 @@ public static class Builder { /** * Add specific Claims to set as the Header. * If provided map is null then nothing is changed - * If provided map contains a claim with null value then that claim will be removed from the header * * @param headerClaims the values to use as Claims in the token's Header. * @return this same Builder instance. @@ -362,7 +361,6 @@ public Builder withClaim(String name, Map map) throws IllegalArgument * @return this same Builder instance. * @throws IllegalArgumentException if the name is null, or if the list contents does not validate. */ - public Builder withClaim(String name, List list) throws IllegalArgumentException { assertNonNull(name); // validate list contents @@ -374,6 +372,19 @@ public Builder withClaim(String name, List list) throws IllegalArgumentExcept return this; } + /** + * Add a custom claim with null value. + * + * @param name the Claim's name. + * @return this same Builder instance. + * @throws IllegalArgumentException if the name is null + */ + public Builder withNullClaim(String name) throws IllegalArgumentException { + assertNonNull(name); + addClaim(name, null); + return this; + } + /** * Add a custom Array Claim with the given items. * @@ -422,8 +433,8 @@ public Builder withArrayClaim(String name, Long[] items) throws IllegalArgumentE *

* Accepted types are {@linkplain Map} and {@linkplain List} with basic types * {@linkplain Boolean}, {@linkplain Integer}, {@linkplain Long}, {@linkplain Double}, - * {@linkplain String} and {@linkplain Date}. {@linkplain Map}s cannot contain null keys or values. - * {@linkplain List}s can contain null elements. + * {@linkplain String} and {@linkplain Date}. + * {@linkplain Map}s and {@linkplain List}s can contain null elements. *

* *

@@ -442,7 +453,7 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE if (!validatePayload(payloadClaims)) { throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " - + "Long, Double, String and Date"); + + "Long, Double, String, Date and Null"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder @@ -463,7 +474,7 @@ private boolean validatePayload(Map payload) { return false; } else if (value instanceof Map && !validateClaim((Map) value)) { return false; - } else if (value != null && !isSupportedType(value)) { + } else if (!isSupportedType(value)) { return false; } } @@ -474,7 +485,7 @@ private static boolean validateClaim(Map map) { // do not accept null values in maps for (Entry entry : map.entrySet()) { Object value = entry.getValue(); - if (value == null || !isSupportedType(value)) { + if (!isSupportedType(value)) { return false; } @@ -488,7 +499,7 @@ private static boolean validateClaim(Map map) { private static boolean validateClaim(List list) { // accept null values in list for (Object object : list) { - if (object != null && !isSupportedType(object)) { + if (!isSupportedType(object)) { return false; } } @@ -506,13 +517,17 @@ private static boolean isSupportedType(Object value) { } private static boolean isBasicType(Object value) { - Class c = value.getClass(); + if (value == null) { + return true; + } else { + Class c = value.getClass(); - if (c.isArray()) { - return c == Integer[].class || c == Long[].class || c == String[].class; + if (c.isArray()) { + return c == Integer[].class || c == Long[].class || c == String[].class; + } + return c == String.class || c == Integer.class || c == Long.class || c == Double.class + || c == Date.class || c == Instant.class || c == Boolean.class; } - return c == String.class || c == Integer.class || c == Long.class || c == Double.class - || c == Date.class || c == Instant.class || c == Boolean.class; } /** @@ -546,10 +561,6 @@ private void assertNonNull(String name) { } private void addClaim(String name, Object value) { - if (value == null) { - payloadClaims.remove(name); - return; - } payloadClaims.put(name, value); } } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 8b14ff86..947c9e25 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; -import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; @@ -146,7 +145,7 @@ public Verification withJWTId(String jwtId) { public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); withClaim(name, ((claim, decodedJWT) -> { - if (claim instanceof NullClaim) { + if (claim.isMissing()) { throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); } return true; @@ -154,6 +153,13 @@ public Verification withClaimPresence(String name) throws IllegalArgumentExcepti return this; } + @Override + public Verification withNullClaim(String name) throws IllegalArgumentException { + assertNonNull(name); + withClaim(name, ((claim, decodedJWT) -> claim.isNull())); + return this; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -292,7 +298,8 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa } } } else { - claimArr = claim.isNull() ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); + claimArr = claim.isNull() || claim.isMissing() + ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); } List valueArr = Arrays.asList(expectedClaimValue); return claimArr.containsAll(valueArr); 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 54a09575..5bb3dbbc 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -31,32 +31,32 @@ private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { @Override public Boolean asBoolean() { - return !data.isBoolean() ? null : data.asBoolean(); + return isMissing() || isNull() || !data.isBoolean() ? null : data.asBoolean(); } @Override public Integer asInt() { - return !data.isNumber() ? null : data.asInt(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asInt(); } @Override public Long asLong() { - return !data.isNumber() ? null : data.asLong(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asLong(); } @Override public Double asDouble() { - return !data.isNumber() ? null : data.asDouble(); + return isMissing() || isNull() || !data.isNumber() ? null : data.asDouble(); } @Override public String asString() { - return !data.isTextual() ? null : data.asText(); + return isMissing() || isNull() || !data.isTextual() ? null : data.asText(); } @Override public Date asDate() { - if (!data.canConvertToLong()) { + if (isMissing() || isNull() || !data.canConvertToLong()) { return null; } long seconds = data.asLong(); @@ -65,7 +65,7 @@ public Date asDate() { @Override public Instant asInstant() { - if (!data.canConvertToLong()) { + if (isMissing() || isNull() || !data.canConvertToLong()) { return null; } long seconds = data.asLong(); @@ -75,7 +75,7 @@ public Instant asInstant() { @Override @SuppressWarnings("unchecked") public T[] asArray(Class clazz) throws JWTDecodeException { - if (!data.isArray()) { + if (isMissing() || isNull() || !data.isArray()) { return null; } @@ -92,7 +92,7 @@ public T[] asArray(Class clazz) throws JWTDecodeException { @Override public List asList(Class clazz) throws JWTDecodeException { - if (!data.isArray()) { + if (isMissing() || isNull() || !data.isArray()) { return null; } @@ -109,7 +109,7 @@ public List asList(Class clazz) throws JWTDecodeException { @Override public Map asMap() throws JWTDecodeException { - if (!data.isObject()) { + if (isMissing() || isNull() || !data.isObject()) { return null; } @@ -126,6 +126,9 @@ public Map asMap() throws JWTDecodeException { @Override public T as(Class clazz) throws JWTDecodeException { try { + if (isMissing() || isNull()) { + return null; + } return objectReader.treeAsTokens(data).readValueAs(clazz); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); @@ -134,11 +137,21 @@ public T as(Class clazz) throws JWTDecodeException { @Override public boolean isNull() { - return false; + return !isMissing() && data.isNull(); + } + + @Override + public boolean isMissing() { + return data == null || data.isMissingNode(); } @Override public String toString() { + if (isMissing()) { + return "Missing claim"; + } else if (isNull()) { + return "Null claim"; + } return data.toString(); } @@ -161,10 +174,8 @@ static Claim extractClaim(String claimName, Map tree, ObjectRe * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { - if (node == null || node.isNull() || node.isMissingNode()) { - return new NullClaim(); - } return new JsonNodeClaim(node, objectReader); } } +//todo test all as* methods in JsonNodeClaim to ensure isMissing isNull calls are made \ No newline at end of file diff --git a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java b/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java deleted file mode 100644 index 2975b51b..00000000 --- a/lib/src/main/java/com/auth0/jwt/impl/NullClaim.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.auth0.jwt.impl; - -import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.interfaces.Claim; - -import java.time.Instant; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** - * The {@code NullClaim} class is a Claim implementation that returns null when any of it's methods is called. - */ -public class NullClaim implements Claim { - @Override - public boolean isNull() { - return true; - } - - @Override - public Boolean asBoolean() { - return null; - } - - @Override - public Integer asInt() { - return null; - } - - @Override - public Long asLong() { - return null; - } - - @Override - public Double asDouble() { - return null; - } - - @Override - public String asString() { - return null; - } - - @Override - public Date asDate() { - return null; - } - - @Override - public Instant asInstant() { - return null; - } - - @Override - public T[] asArray(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public List asList(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public Map asMap() throws JWTDecodeException { - return null; - } - - @Override - public T as(Class clazz) throws JWTDecodeException { - return null; - } - - @Override - public String toString() { - return "Null Claim"; - } -} diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index fdfac715..f6ccfdcd 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -14,11 +14,20 @@ public interface Claim { /** * Whether this Claim has a null value or not. + * If the claim is not present, it will return false hence checking {@link Claim#isMissing} is advised as well * * @return whether this Claim has a null value or not. */ boolean isNull(); + /** + * Can be used to verify whether the Claim is found or not. + * This will be true even if the Claim has null value associated to it. + * + * @return whether this Claim is present or not + */ + boolean isMissing(); + /** * Get this Claim as a Boolean. * If the value isn't of type Boolean or it can't be converted to a Boolean, null will be returned. @@ -110,6 +119,7 @@ default Instant asInstant() { /** * Get this Claim as a custom type T. + * This method will return null if {@link Claim#isMissing()} or {@link Claim#isNull()} is true * * @param type * @param clazz the type class diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index ffe0b920..8b0416dc 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -132,6 +132,15 @@ default Verification withAnyOfAudience(String... audience) { */ Verification withClaimPresence(String name) throws IllegalArgumentException; + /** + * Require a specific Claim value to be null. + * + * @param name the Claim's name. + * @return this same Verification instance. + * @throws IllegalArgumentException if the name is null. + */ + Verification withNullClaim(String name) throws IllegalArgumentException; + /** * Require a specific Claim value. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 6c870656..3cccbf7b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -326,17 +326,6 @@ public void shouldAddJWTId() { assertThat(TokenUtils.splitToken(signed)[1], is("eyJqdGkiOiJqd3RfaWRfMTIzIn0")); } - @Test - public void shouldRemoveClaimWhenPassingNull() { - String signed = JWTCreator.init() - .withIssuer("iss") - .withIssuer(null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(signed, is(notNullValue())); - assertThat(TokenUtils.splitToken(signed)[1], is("e30")); - } - @Test public void shouldSetCorrectAlgorithmInTheHeader() { String signed = JWTCreator.init() @@ -615,7 +604,7 @@ public void shouldAcceptCustomListClaimOfBasicObjectTypes() throws Exception { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); - + String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); List list = (List) mapper.readValue(body, Map.class).get("data"); @@ -648,52 +637,6 @@ public void shouldAcceptCustomClaimForNullListItem() { .sign(Algorithm.HMAC256("secret")); } - @Test - @SuppressWarnings("unchecked") - public void shouldAcceptCustomClaimWithNullMapAndRemoveClaim() throws Exception { - String jwt = JWTCreator.init() - .withClaim("map", "stubValue") - .withClaim("map", (Map) null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldAcceptCustomClaimWithNullListAndRemoveClaim() throws Exception { - String jwt = JWTCreator.init() - .withClaim("list", "stubValue") - .withClaim("list", (List) null) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - - @Test - public void shouldRefuseCustomClaimForNullMapValue() { - Map data = new HashMap<>(); - data.put("subKey", null); - - exception.expect(IllegalArgumentException.class); - - JWTCreator.init() - .withClaim("pojo", data) - .sign(Algorithm.HMAC256("secret")); - } - @Test public void shouldRefuseCustomClaimForNullMapKey() { Map data = new HashMap<>(); @@ -814,26 +757,10 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); } - @Test - public void shouldRemovePayloadIfTheValueIsNull() throws Exception { - String jwt = JWTCreator.init() - .withClaim("key", "stubValue") - .withPayload(Collections.singletonMap("key", (Map) null)) - .sign(Algorithm.HMAC256("secret")); - - assertThat(jwt, is(notNullValue())); - String[] parts = jwt.split("\\."); - - String body = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - ObjectMapper mapper = new ObjectMapper(); - Map map = (Map) mapper.readValue(body, Map.class); - assertThat(map, anEmptyMap()); - } - @Test public void withPayloadShouldNotAllowCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -860,7 +787,7 @@ public void withPayloadShouldAllowNullListItems() { @Test public void withPayloadShouldNotAllowListWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); @@ -872,7 +799,7 @@ public void withPayloadShouldNotAllowListWithCustomType() { @Test public void withPayloadShouldNotAllowMapWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -922,4 +849,75 @@ public void withPayloadShouldAllowNestedSupportedTypes() { assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", mapClaim)); } + + @Test + public void withPayloadShouldSupportNullValuesEverywhere() { + /* + JWT: + { + "listClaim": [ + "answer to ultimate question of life", + 42, + null + ], + "claim": null, + "listNestedClaim": [ + 1, + 2, + { + "nestedObjKey": null + } + ], + "objClaim": { + "nestedObjKey": null, + "objObjKey": { + "nestedObjKey": null, + "objListKey": [ + null, + "nestedList2" + ] + }, + "objListKey": [ + null, + "nestedList2" + ] + } + } + */ + + List listClaim = Arrays.asList("answer to ultimate question of life", 42, null); + List listNestedClaim = Arrays.asList(1, 2, Collections.singletonMap("nestedObjKey", null)); + List objListKey = Arrays.asList(null, "nestedList2"); + HashMap objClaim = new HashMap<>(); + objClaim.put("nestedObjKey", null); + objClaim.put("objListKey", objListKey); + objClaim.put("objObjKey", new HashMap<>(objClaim)); + + + Map payload = new HashMap<>(); + payload.put("claim", null); + payload.put("listClaim", listClaim); + payload.put("listNestedClaim", listNestedClaim); + payload.put("objClaim", objClaim); + + String jwt = JWTCreator.init() + .withPayload(payload) + .withHeader(payload) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + + assertThat(payloadJson, JsonMatcher.hasEntry("claim", null)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("objClaim", objClaim)); + + assertThat(headerJson, JsonMatcher.hasEntry("claim", null)); + assertThat(headerJson, JsonMatcher.hasEntry("listClaim", listClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); + } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 32db703e..5ea2b4ef 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -1,7 +1,6 @@ package com.auth0.jwt; import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.hamcrest.collection.IsCollectionWithSize; @@ -216,7 +215,8 @@ public void shouldGetMissingClaimIfClaimDoesNotExist() { DecodedJWT jwt = JWT.decode("eyJhbGciOiJIUzI1NiJ9.e30.K17vlwhE8FCMShdl1_65jEYqsQqBOVMPUU9IgG-QlTM"); assertThat(jwt, is(notNullValue())); assertThat(jwt.getClaim("notExisting"), is(notNullValue())); - assertThat(jwt.getClaim("notExisting"), is(instanceOf(NullClaim.class))); + assertThat(jwt.getClaim("notExisting").isMissing(), is(true)); + assertThat(jwt.getClaim("notExisting").isNull(), is(false)); } @Test @@ -295,13 +295,28 @@ public void shouldGetCustomArrayClaimOfTypeInteger() { @Test public void shouldGetCustomMapClaim() { - String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InN0cmluZyI6InZhbHVlIiwibnVtYmVyIjoxLCJib29sZWFuIjp0cnVlfX0.-8aIaXd2-rp1lLuDEQmCeisCBX9X_zbqdPn2llGxNoc"; + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjp7InN0cmluZyI6InZhbHVlIiwibnVtYmVyIjoxLCJib29sZWFuIjp0cnVlLCJlbXB0eSI6bnVsbH19.6xkCuYZnu4RA0xZSxlYSYAqzy9JDWsDtIWqSCUZlPt8"; DecodedJWT jwt = JWT.decode(token); assertThat(jwt, is(notNullValue())); Map map = jwt.getClaim("name").asMap(); assertThat(map, hasEntry("string", "value")); assertThat(map, hasEntry("number", 1)); assertThat(map, hasEntry("boolean", true)); + assertThat(map, hasEntry("empty", null)); + } + + @Test + public void shouldGetCustomNullClaim() { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpudWxsfQ.X4ALHe7uYqEcXWFBnwBUNRKwmwrtDEGZ2aynRYYUx8c"; + DecodedJWT jwt = JWT.decode(token); + assertThat(jwt.getClaim("name").isNull(), is(true)); + } + + @Test + public void shouldGetListClaim() { + String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbbnVsbCwiaGVsbG8iXX0.SpcuQRBGdTV0ofHdxBSnhWEUsQi89noZUXin2Thwb70"; + DecodedJWT jwt = JWT.decode(token); + assertThat(jwt.getClaim("name").asList(String.class), contains(null, "hello")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1d80d802..7d2123a9 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1064,4 +1064,47 @@ public void shouldRemovePredicateCheckForNull() { assertThat(verifier.expectedChecks, not(hasKey("claimName"))); } + @Test + public void shouldSuccessfullyVerifyClaimWithNull() { + String jwt = JWTCreator.init() + .withNullClaim("claimName") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldThrowWhenNullClaimHasValue() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build() + .verify(jwt); + } + + @Test + public void shouldThrowWhenNullClaimIsMissing() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'anotherClaimName' value doesn't match the required one."); + + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("anotherClaimName") + .build() + .verify(jwt); + } } diff --git a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java index e3d04c77..c4a04d81 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/BasicHeaderTest.java @@ -135,6 +135,7 @@ public void shouldGetNotNullExtraClaimIfMissing() { assertThat(header, is(notNullValue())); assertThat(header.getHeaderClaim("missing"), is(notNullValue())); - assertThat(header.getHeaderClaim("missing"), is(instanceOf(NullClaim.class))); + assertThat(header.getHeaderClaim("missing").isMissing(), is(true)); + assertThat(header.getHeaderClaim("missing").isNull(), is(false)); } } \ No newline at end of file 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 3c744487..44ef2b9f 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -331,8 +331,8 @@ public void shouldReturnBaseClaimWhenParsingMissingNode() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); - assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(true)); + assertThat(claim.isNull(), is(false)); } @Test @@ -341,8 +341,8 @@ public void shouldReturnBaseClaimWhenParsingNullNode() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -351,8 +351,8 @@ public void shouldReturnBaseClaimWhenParsingNullValue() { Claim claim = claimFromNode(value); assertThat(claim, is(notNullValue())); - assertThat(claim, is(instanceOf(NullClaim.class))); assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -363,6 +363,7 @@ public void shouldReturnNonNullClaimWhenParsingObject() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -373,6 +374,7 @@ public void shouldReturnNonNullClaimWhenParsingArray() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -383,6 +385,7 @@ public void shouldReturnNonNullClaimWhenParsingList() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -393,6 +396,7 @@ public void shouldReturnNonNullClaimWhenParsingStringValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -403,6 +407,7 @@ public void shouldReturnNonNullClaimWhenParsingIntValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -413,6 +418,7 @@ public void shouldReturnNonNullClaimWhenParsingDoubleValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -423,6 +429,7 @@ public void shouldReturnNonNullClaimWhenParsingDateValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -433,6 +440,18 @@ public void shouldReturnNonNullClaimWhenParsingBooleanValue() { assertThat(claim, is(notNullValue())); assertThat(claim, is(instanceOf(JsonNodeClaim.class))); assertThat(claim.isNull(), is(false)); + assertThat(claim.isMissing(), is(false)); + } + + @Test + public void shouldReturnNullIsTrue() { + JsonNode value = mapper.valueToTree(null); + Claim claim = claimFromNode(value); + + assertThat(claim, is(notNullValue())); + assertThat(claim, is(instanceOf(JsonNodeClaim.class))); + assertThat(claim.isNull(), is(true)); + assertThat(claim.isMissing(), is(false)); } @Test @@ -441,4 +460,22 @@ public void shouldDelegateToJsonNodeToString() { Claim claim = claimFromNode(value); assertThat(claim.toString(), is(value.toString())); } + + @Test + public void shouldConvertToString() { + JsonNode value = mapper.valueToTree(new UserPojo("john", 123)); + JsonNode nullValue = mapper.valueToTree(null); + JsonNode missingValue = MissingNode.getInstance(); + + Claim claim = claimFromNode(value); + Claim nullClaim = claimFromNode(nullValue); + Claim missingClaim = claimFromNode(missingValue); + + assertThat(claim.toString(), is("{\"name\":\"john\",\"id\":123}")); + assertThat(nullClaim.isNull(), is(true)); + assertThat(nullClaim.toString(), is("Null claim")); + assertThat(missingClaim.isMissing(), is(true)); + assertThat(missingClaim.toString(), is("Missing claim")); + + } } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java deleted file mode 100644 index bd478130..00000000 --- a/lib/src/test/java/com/auth0/jwt/impl/NullClaimTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.auth0.jwt.impl; - -import org.junit.Before; -import org.junit.Test; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class NullClaimTest { - private NullClaim claim; - - @Before - public void setUp() { - claim = new NullClaim(); - } - - @Test - public void shouldBeNull() { - assertThat(claim.isNull(), is(true)); - } - - @Test - public void shouldGetAsBoolean() { - assertThat(claim.asBoolean(), is(nullValue())); - } - - @Test - public void shouldGetAsInt() { - assertThat(claim.asInt(), is(nullValue())); - } - - @Test - public void shouldGetAsLong() { - assertThat(claim.asLong(), is(nullValue())); - } - - @Test - public void shouldGetAsDouble() { - assertThat(claim.asDouble(), is(nullValue())); - } - - @Test - public void shouldGetAsString() { - assertThat(claim.asString(), is(nullValue())); - } - - @Test - public void shouldGetAsDate() { - assertThat(claim.asDate(), is(nullValue())); - } - - @Test - public void shouldGetAsInstant() { - assertThat(claim.asInstant(), is(nullValue())); - } - - @Test - public void shouldGetAsArray() { - assertThat(claim.asArray(Object.class), is(nullValue())); - } - - @Test - public void shouldGetAsList() { - assertThat(claim.asList(Object.class), is(nullValue())); - } - - @Test - public void shouldGetAsMap() { - assertThat(claim.asMap(), is(nullValue())); - } - - @Test - public void shouldGetAsCustomClass() { - assertThat(claim.as(Object.class), is(nullValue())); - } - - @Test - public void shouldHaveToString() { - assertThat(claim.toString(), is("Null Claim")); - } -} \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index 9e2e6902..da0c880e 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -166,7 +166,8 @@ public void shouldGetNotNullExtraClaimIfMissing() { PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); - assertThat(payload.getClaim("missing"), is(instanceOf(NullClaim.class))); + assertThat(payload.getClaim("missing").isMissing(), is(true)); + assertThat(payload.getClaim("missing").isNull(), is(false)); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java index 77d573bc..61541ccf 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/ClaimTest.java @@ -40,6 +40,11 @@ public boolean isNull() { return false; } + @Override + public boolean isMissing() { + return false; + } + @Override public Boolean asBoolean() { return null; diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index c7f89763..fb0c74a9 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -104,6 +104,11 @@ public Verification withClaimPresence(String name) throws IllegalArgumentExcepti return null; } + @Override + public Verification withNullClaim(String name) throws IllegalArgumentException { + return null; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { return null; From 67df3d476024cbe697b3a54893f5383945c7670d Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 29 Mar 2022 13:08:11 +0530 Subject: [PATCH 239/355] [SDK-3154] Improved Exception Handling (#568) * Improved exception handling * Fixed issues thrown by Linter * Fixed Lint Issue (Missing Period) * Improved Code Coverage --- .../main/java/com/auth0/jwt/JWTVerifier.java | 85 ++++++---- .../exceptions/IncorrectClaimException.java | 44 +++++ .../jwt/exceptions/InvalidClaimException.java | 2 +- .../jwt/exceptions/MissingClaimException.java | 23 +++ .../jwt/exceptions/TokenExpiredException.java | 11 +- .../com/auth0/jwt/impl/JsonNodeClaim.java | 3 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 159 ++++++++++++++---- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 43 +++++ .../auth0/jwt/matchers/CustomMatchers.java | 114 +++++++++++++ 9 files changed, 410 insertions(+), 74 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/IncorrectClaimException.java create mode 100644 lib/src/main/java/com/auth0/jwt/exceptions/MissingClaimException.java create mode 100644 lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 947c9e25..ba19feb9 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -72,7 +72,8 @@ public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { if (value == null || !value.contains(claim.asString())) { - throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); + throw new IncorrectClaimException( + "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); } return true; })); @@ -89,7 +90,7 @@ public Verification withSubject(String subject) { public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(decodedJWT.getAudience(), value, true))); + assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, true))); return this; } @@ -97,7 +98,7 @@ public Verification withAudience(String... audience) { public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(decodedJWT.getAudience(), value, false))); + assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, false))); return this; } @@ -144,12 +145,7 @@ public Verification withJWTId(String jwtId) { @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - withClaim(name, ((claim, decodedJWT) -> { - if (claim.isMissing()) { - throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", name)); - } - return true; - })); + withClaim(name, ((claim, decodedJWT) -> assertClaimPresence(name, claim))); return this; } @@ -272,13 +268,13 @@ private void addMandatoryClaimChecks() { long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - checkIfNeedToRemove(PublicClaims.EXPIRES_AT, expiresAtLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), expiresAtLeeway, true))); - checkIfNeedToRemove(PublicClaims.NOT_BEFORE, notBeforeLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), notBeforeLeeway, false))); + expectedChecks.put(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true)); + expectedChecks.put(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false)); if (!ignoreIssuedAt) { - checkIfNeedToRemove(PublicClaims.ISSUED_AT, issuedAtLeeway, ((claim, decodedJWT) -> - assertValidInstantClaim(claim.asInstant(), issuedAtLeeway, false))); + expectedChecks.put(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false)); } } @@ -305,33 +301,50 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa return claimArr.containsAll(valueArr); } - private boolean assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) { + private boolean assertValidInstantClaim(String claimName, Claim claim, long leeway, boolean shouldBeFuture) { + Instant claimVal = claim.asInstant(); Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS); + boolean isValid; if (shouldBeFuture) { - return assertInstantIsFuture(claimVal, leeway, now); + isValid = assertInstantIsFuture(claimVal, leeway, now); + if (!isValid) { + throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal), claimVal); + } } else { - return assertInstantIsPast(claimVal, leeway, now); + isValid = assertInstantIsPast(claimVal, leeway, now); + if (!isValid) { + throw new IncorrectClaimException( + String.format("The Token can't be used before %s.", claimVal), claimName, claim); + } } + return true; } private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) { - throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal)); - } - return true; + return !(claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)); } private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { - if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) { - throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal)); - } - return true; + return !(claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)); } - private boolean assertValidAudienceClaim(List audience, List values, boolean shouldContainAll) { + private boolean assertValidAudienceClaim( + Claim claim, + List audience, + List values, + boolean shouldContainAll + ) { if (audience == null || (shouldContainAll && !audience.containsAll(values)) || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new InvalidClaimException("The Claim 'aud' value doesn't contain the required audience."); + throw new IncorrectClaimException( + "The Claim 'aud' value doesn't contain the required audience.", PublicClaims.AUDIENCE, claim); + } + return true; + } + + private boolean assertClaimPresence(String name, Claim claim) { + if (claim.isMissing()) { + throw new MissingClaimException(name); } return true; } @@ -353,7 +366,8 @@ private void checkIfNeedToRemove(String name, Object value, BiPredicate assertClaimPresence(name, claim) + && predicate.test(claim, decodedJWT)); } private boolean isNullOrEmpty(String[] args) { @@ -381,7 +395,8 @@ private boolean isNullOrEmpty(String[] args) { * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. - * @throws InvalidClaimException if a claim contained a different value than the expected one. + * @throws MissingClaimException if a claim to be verified is missing. + * @throws IncorrectClaimException if a claim contained a different value than the expected one. */ @Override public DecodedJWT verify(String token) throws JWTVerificationException { @@ -398,7 +413,8 @@ public DecodedJWT verify(String token) throws JWTVerificationException { * the one defined in the {@link JWTVerifier}. * @throws SignatureVerificationException if the signature is invalid. * @throws TokenExpiredException if the token has expired. - * @throws InvalidClaimException if a claim contained a different value than the expected one. + * @throws MissingClaimException if a claim to be verified is missing. + * @throws IncorrectClaimException if a claim contained a different value than the expected one. */ @Override public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException { @@ -426,8 +442,11 @@ private void verifyClaims(DecodedJWT jwt, Map hasMissingClaimName(final String claimName) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("MissingClaimException with claim name: "+claimName); + } + + @Override + protected boolean matchesSafely(MissingClaimException item) { + return item.getClaimName().equals(claimName); + } + }; + } + + public static Matcher hasTokenExpiredOn(final Instant instant) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("TokenExpiredException with expired time as: "+instant.getEpochSecond()); + } + + @Override + protected boolean matchesSafely(TokenExpiredException item) { + return item.getExpiredOn().equals(instant); + } + }; + } + + public static Matcher hasClaimName(final String claimName) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim name: "+claimName); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimName().equals(claimName); + } + }; + } + + public static Matcher hasClaimValue(final Object value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimValue().as(clazz).equals(value); + } + }; + } + + public static Matcher hasClaimInstant(final Instant value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return item.getClaimValue().as(clazz).equals(value); + } + }; + } + + public static Matcher hasClaimValueArray(final Object value, final Class clazz) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim : "+value); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + return Arrays.equals((Object[]) item.getClaimValue().as(clazz), (Object[])value); + } + }; + } + + public static Matcher hasNullClaim() { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("IncorrectClaimException with claim as null"); + } + + @Override + protected boolean matchesSafely(IncorrectClaimException item) { + boolean a = item.getClaimValue().isNull(); + String b = item.getClaimValue().toString(); + return item.getClaimValue().isNull(); + } + }; + } +} From dd22f32d5cfcffd6066a5ce949bcc58481cc2f5a Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 30 Mar 2022 03:35:54 -0500 Subject: [PATCH 240/355] Security: Bump `jackson-databind` to 2.13.2.2 (#566) * Security: Bump `jackson-databind` to 2.13.2.1 This PR bumps the `jackson-databind` dependency to 2.13.2.1 to address [CVE-2020-36518](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-36518) in that library * Bump to 2.13.2.2 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 55f531b8..fbc88df8 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -47,7 +47,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.3' From 9793478af856483af7b1e75cd8d66096df77b18f Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 30 Mar 2022 14:09:19 +0530 Subject: [PATCH 241/355] Release 3.19.1 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84686b4b..a022328d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.19.1](https://github.com/auth0/java-jwt/tree/3.19.1) (2022-03-30) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.0...3.19.1) + +**Security** +- Security: Bump `jackson-databind` to 2.13.2.2 [\#566](https://github.com/auth0/java-jwt/pull/566) ([evansims](https://github.com/evansims)) + ## [3.19.0](https://github.com/auth0/java-jwt/tree/3.19.0) (2022-03-14) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.18.3...3.19.0) diff --git a/README.md b/README.md index a26170d2..260f6d6e 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.19.0 + 3.19.1 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.0' +implementation 'com.auth0:java-jwt:3.19.1' ``` ## Available Algorithms From ed58ef33d1ed800b84678acee2cab172684198d1 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 1 Apr 2022 15:35:37 +0530 Subject: [PATCH 242/355] Improved README structure --- README.md | 193 +++++++++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 0f847cca..fb489d9b 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,29 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o > This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. +## Table of Contents +- [**Installation**](#installation) +- [**Available Algorithms**](#available-algorithms) +- [**Quickstart**](#quickstart) + + [**Create and Sign a Token**](#create-and-sign-a-token) + + [**Verify a Token**](#verify-a-token) + + [**Decode a Token**](#decode-a-token) +- [**Usage**](#usage) + + [**Pick the algorithm**](#pick-the-algorithm) + + [**Time Validation**](#time-validation) + + [**Header Claims**](#header-claims) + + [**Payload Claims**](#payload-claims) + + [**Claim Class**](#claim-class) +- [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) + ## Installation -The library is available on both Maven Central and Bintray, and the Javadoc is published [here](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html). - +### Gradle + +```gradle +implementation 'com.auth0:java-jwt:3.19.0' +``` + ### Maven ```xml @@ -27,12 +46,6 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p ``` -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:3.19.0' -``` - ## Available Algorithms The library implements JWT Verification and Signing using the following algorithms: @@ -49,73 +62,9 @@ The library implements JWT Verification and Signing using the following algorith | ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 | | ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | -⚠️ Note - ECDSA with curve secp256k1 and SHA-256 will not be supported for Java 15+ by this library since it has been (disabled in Java 15)[https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219] - -## Usage - -### Pick the Algorithm - -The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. - -When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. - - -#### Using static secrets or keys: - -```java -//HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); - -//RSA -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); -``` - -> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). - -##### HMAC Key Length and Security - -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. - -#### Using a KeyProvider: - -By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - -- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). -- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. -- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. - - -The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. - -```java -final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); -final RSAPrivateKey privateKey = //Get the key instance -final String privateKeyId = //Create an Id for the above key +> Note - Support for ECDSA with curve secp256k1 and SHA-256 (ES256K) has been dropped since it has been [disabled in Java 15](https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219) -RSAKeyProvider keyProvider = new RSAKeyProvider() { - @Override - public RSAPublicKey getPublicKeyById(String kid) { - //Received 'kid' value might be null if it wasn't defined in the Token's header - RSAPublicKey publicKey = jwkStore.get(kid); - return (RSAPublicKey) publicKey; - } - - @Override - public RSAPrivateKey getPrivateKey() { - return privateKey; - } - - @Override - public String getPrivateKeyId() { - return privateKeyId; - } -}; - -Algorithm algorithm = Algorithm.RSA256(keyProvider); -//Use the Algorithm to create and verify JWTs. -``` +## Quickstart ### Create and Sign a Token @@ -151,7 +100,6 @@ You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. U If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. - ### Verify a Token You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. @@ -190,8 +138,87 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. +### Decode a Token -#### Time Validation +```java +String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; +try { + DecodedJWT jwt = JWT.decode(token); +} catch (JWTDecodeException exception){ + //Invalid token +} +``` + +If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. + +## Usage + +### Pick the Algorithm + +The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. + +When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. + + +#### Using static secrets or keys: + +```java +//HMAC +Algorithm algorithmHS = Algorithm.HMAC256("secret"); + +//RSA +RSAPublicKey publicKey = //Get the key instance +RSAPrivateKey privateKey = //Get the key instance +Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); +``` + +> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). + +##### HMAC Key Length and Security + +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. + +#### Using a KeyProvider: +//todo need to be updated +By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: + +- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). +- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. +- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. + + +The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. + +```java +final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); +final RSAPrivateKey privateKey = //Get the key instance +final String privateKeyId = //Create an Id for the above key + +RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKeyById(String kid) { + //Received 'kid' value might be null if it wasn't defined in the Token's header + RSAPublicKey publicKey = jwkStore.get(kid); + return (RSAPublicKey) publicKey; + } + + @Override + public RSAPrivateKey getPrivateKey() { + return privateKey; + } + + @Override + public String getPrivateKeyId() { + return privateKeyId; + } +}; + +Algorithm algorithm = Algorithm.RSA256(keyProvider); +//Use the Algorithm to create and verify JWTs. +``` + + +### Time Validation The JWT token may include DateNumber fields that can be used to validate that: * The token was issued in a past date `"iat" < TODAY` @@ -227,20 +254,6 @@ Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock); ``` -### Decode a Token - -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - DecodedJWT jwt = JWT.decode(token); -} catch (JWTDecodeException exception){ - //Invalid token -} -``` - -If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. - - ### Header Claims #### Algorithm ("alg") From c1942944b3b906d749128279808ad86cd24b8b9f Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 12 Apr 2022 18:55:31 +0530 Subject: [PATCH 243/355] Add README instructions for new APIs --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb489d9b..065899f1 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,6 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: -//todo need to be updated By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). @@ -388,6 +387,7 @@ When creating a Token with the `JWT.create()` you can specify a custom Claim by String token = JWT.create() .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) + .withNullClaim("claim_name") .sign(algorithm); ``` @@ -407,6 +407,9 @@ You can also verify custom Claims on the `JWT.require()` by calling `withClaim() JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) + .withNullClaim("null_value") //checks if the claim name provided has null value + .withClaimPresence("claim_presence") //checks if the claim name provided is in the payload + .withClaim("predicate", (claim, decodedJWT) -> "custom_check".equals(claim.asString())) //can be used to run custom verification .build(); DecodedJWT jwt = verifier.verify("my.jwt.token"); ``` @@ -423,7 +426,10 @@ The Claim class is a wrapper for the Claim values. It allows you to get the Clai * **asDouble()**: Returns the Double value or null if it can't be converted. * **asLong()**: Returns the Long value or null if it can't be converted. * **asString()**: Returns the String value or null if it can't be converted. -* **asDate()**: Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. +* **asInstant()**: Returns the Instant value or null if it can't be converted. +* **asDate()**: Returns the Date value or null if it can't be converted. + +> For `asInstant()` and `asDate()` the value must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. #### Custom Classes and Collections To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. From 0f8a9bf20f215f9cdb78c50d7edbfe9d811cfdbc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 12 Apr 2022 19:18:46 +0530 Subject: [PATCH 244/355] Replace expected exception with assertThrows (#572) * Replace expected exception with assertThrows --- .../java/com/auth0/jwt/JWTVerifierTest.java | 550 +++++++++--------- .../auth0/jwt/matchers/CustomMatchers.java | 114 ---- 2 files changed, 275 insertions(+), 389 deletions(-) delete mode 100644 lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 1cf79c8f..9dcb2cb1 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -19,9 +19,9 @@ import java.util.Date; import java.util.function.BiPredicate; -import static com.auth0.jwt.matchers.CustomMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; public class JWTVerifierTest { @@ -35,17 +35,18 @@ public class JWTVerifierTest { @Test public void shouldThrowWhenInitializedWithoutAlgorithm() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("The Algorithm cannot be null"); - JWTVerifier.init(null); + IllegalArgumentException e = assertThrows(null, IllegalArgumentException.class, () -> + JWTVerifier.init(null)); + assertThat(e.getMessage(), is("The Algorithm cannot be null.")); } @Test public void shouldThrowWhenAlgorithmDoesntMatchTheTokensAlgorithm() { - exception.expect(AlgorithmMismatchException.class); - exception.expectMessage("The provided Algorithm doesn't match the one defined in the JWT's Header."); - JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC512("secret")).build(); - verifier.verify("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"); + AlgorithmMismatchException e = assertThrows(null, AlgorithmMismatchException.class, () -> { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC512("secret")).build(); + verifier.verify("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho"); + }); + assertThat(e.getMessage(), is("The provided Algorithm doesn't match the one defined in the JWT's Header.")); } @Test @@ -73,45 +74,45 @@ public void shouldValidateMultipleIssuers() { @Test public void shouldThrowOnInvalidIssuer() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); - exception.expect(hasClaimName(PublicClaims.ISSUER)); - exception.expect(hasClaimValue("auth0", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimValue().asString(), is("auth0")); } @Test public void shouldThrowOnNullIssuer() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'iss' value doesn't match the required issuer."); - exception.expect(hasClaimName(PublicClaims.ISSUER)); - exception.expect(hasNullClaim()); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("auth0") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("auth0") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimValue().isNull(), is(true)); } @Test public void shouldThrowOnMissingIssuer() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'iss' is not present in the JWT."); - exception.expect(hasMissingClaimName("iss")); - - String jwt = JWTCreator.init() - .sign(Algorithm.HMAC256("secret")); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .sign(Algorithm.HMAC256("secret")); - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withIssuer("nope") - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer("nope") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'iss' is not present in the JWT.")); + assertThat(e.getClaimName(), is("iss")); } @Test @@ -127,16 +128,16 @@ public void shouldValidateSubject() { @Test public void shouldThrowOnInvalidSubject() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'sub' value doesn't match the required one."); - exception.expect(hasClaimName(PublicClaims.SUBJECT)); - exception.expect(hasClaimValue("1234567890", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withSubject("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withSubject("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); + assertThat(e.getClaimName(), is(PublicClaims.SUBJECT)); + assertThat(e.getClaimValue().asString(), is("1234567890")); } @Test @@ -230,76 +231,75 @@ public void shouldAcceptAudienceWhenAnyOfAudienceAndAllContained() { @Test public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {"Mark","David","John"}, String[].class)); - - // Token 'aud' = ["Mark", "David", "John"] - String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("Joe", "Jim") - .build() - .verify(tokenArr); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud' = ["Mark", "David", "John"] + String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("Joe", "Jim") + .build() + .verify(tokenArr); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @Test public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {"Mark","David","John"}, String[].class)); - - // Token 'aud' = ["Mark", "David", "John"] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("Mark", "Joe") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud' = ["Mark", "David", "John"] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("Mark", "Joe") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @Test public void shouldThrowWhenAudienceClaimIsNull() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasNullClaim()); - - // Token 'aud': null - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("nope") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().isNull(), is(true)); } @Test public void shouldThrowWhenAudienceClaimIsMissing(){ - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'aud' is not present in the JWT."); - exception.expect(hasMissingClaimName("aud")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAudience("nope") - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' is not present in the JWT.")); + assertThat(e.getClaimName(), is("aud")); } @Test public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'aud' value doesn't contain the required audience."); - exception.expect(hasClaimName(PublicClaims.AUDIENCE)); - exception.expect(hasClaimValueArray(new String[] {null}, String[].class)); - - // Token 'aud': [null] - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpbbnVsbF19.2cBf7FbkX52h8Vmjnl1DY1PYe_J_YP0KsyeoeYmuca8"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withAnyOfAudience("nope") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': [null] + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpbbnVsbF19.2cBf7FbkX52h8Vmjnl1DY1PYe_J_YP0KsyeoeYmuca8"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience("nope") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @Test @@ -384,113 +384,113 @@ public void shouldThrowOnNullCustomClaimName() { @Test public void shouldThrowWhenExpectedArrayClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withArrayClaim("missing", 1, 2, 3) - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("missing", 1, 2, 3) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test public void shouldThrowWhenExpectedClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("missing", "text") - .build() - .verify(token); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("missing", "text") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeString() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", "value") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "value") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeInteger() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", 123) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", 123) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeDouble() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", 23.45) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", 23.45) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeBoolean() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", true) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", true) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValueOfTypeDate() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", new Date()) - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", new Date()) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test public void shouldThrowOnInvalidCustomClaimValue() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'name' value doesn't match the required one."); - exception.expect(hasClaimName("name")); - exception.expect(hasClaimValueArray(new String[] {"something"}, String[].class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("name", "check") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("name", "check") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("name")); + assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"})); } @Test @@ -734,15 +734,15 @@ public void shouldValidateExpiresAtIfPresent() { @Test public void shouldThrowOnInvalidExpiresAtIfPresent() { - exception.expect(TokenExpiredException.class); - exception.expectMessage(startsWith("The Token has expired on")); - exception.expect(hasTokenExpiredOn(Instant.ofEpochSecond(1477592L))); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondLater) - .verify(token); + TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondLater) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z.")); + assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L))); } @Test @@ -769,16 +769,16 @@ public void shouldValidateNotBeforeWithLeeway() { @Test public void shouldThrowOnInvalidNotBeforeIfPresent() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.NOT_BEFORE)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondEarlier) - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondEarlier) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.NOT_BEFORE)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } @Test @@ -804,16 +804,16 @@ public void shouldThrowOnNegativeNotBeforeLeeway() { // Issued At with future date @Test public void shouldThrowOnFutureIssuedAt() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.ISSUED_AT)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token); - assertThat(jwt, is(notNullValue())); + DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token); + assertThat(jwt, is(notNullValue())); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } // Issued At with future date and ignore flag @@ -829,16 +829,16 @@ public void shouldSkipIssuedAtVerificationWhenFlagIsPassed() { @Test public void shouldThrowOnInvalidIssuedAtIfPresent() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Token can't be used before 1970-01-18T02:26:32Z."); - exception.expect(hasClaimName(PublicClaims.ISSUED_AT)); - exception.expect(hasClaimValue(1477592L, Long.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; - JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); - verification - .build(mockOneSecondEarlier) - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockOneSecondEarlier) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); + assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimValue().asLong(), is(1477592L)); } @Test @@ -887,16 +887,16 @@ public void shouldValidateJWTId() { @Test public void shouldThrowOnInvalidJWTId() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'jti' value doesn't match the required one."); - exception.expect(hasClaimName("jti")); - exception.expect(hasClaimValue("jwt_id_123", String.class)); - - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4"; - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withJWTId("invalid") - .build() - .verify(token); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withJWTId("invalid") + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'jti' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("jti")); + assertThat(e.getClaimValue().asString(), is("jwt_id_123")); } @Test @@ -963,19 +963,19 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() { @Test public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'missing' is not present in the JWT."); - exception.expect(hasMissingClaimName("missing")); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("custom", "") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaimPresence("missing") - .build(); + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("missing") + .build(); - verifier.verify(jwt); + verifier.verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT.")); + assertThat(e.getClaimName(), is("missing")); } @Test @@ -1120,19 +1120,19 @@ public void shouldSuccessfullyVerifyClaimWithPredicate() { @Test public void shouldThrowWhenPredicateReturnsFalse() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); - exception.expect(hasClaimName("claimName")); - exception.expect(hasClaimValue("claimValue", String.class)); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "claimValue") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("claimName", "claimValue") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("claimName")); + assertThat(e.getClaimValue().asString(), is("claimValue")); } @Test @@ -1162,34 +1162,34 @@ public void shouldSuccessfullyVerifyClaimWithNull() { @Test public void shouldThrowWhenNullClaimHasValue() { - exception.expect(IncorrectClaimException.class); - exception.expectMessage("The Claim 'claimName' value doesn't match the required one."); - exception.expect(hasClaimName("claimName")); - exception.expect(hasClaimValue("value", String.class)); + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); - String jwt = JWTCreator.init() - .withClaim("claimName", "value") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withNullClaim("claimName") - .build() - .verify(jwt); + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("claimName") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one.")); + assertThat(e.getClaimName(), is("claimName")); + assertThat(e.getClaimValue().asString(), is("value")); } @Test public void shouldThrowWhenNullClaimIsMissing() { - exception.expect(MissingClaimException.class); - exception.expectMessage("The Claim 'anotherClaimName' is not present in the JWT."); - exception.expect(hasMissingClaimName("anotherClaimName")); - - String jwt = JWTCreator.init() - .withClaim("claimName", "value") - .sign(Algorithm.HMAC256("secret")); - - JWTVerifier.init(Algorithm.HMAC256("secret")) - .withNullClaim("anotherClaimName") - .build() - .verify(jwt); + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String jwt = JWTCreator.init() + .withClaim("claimName", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withNullClaim("anotherClaimName") + .build() + .verify(jwt); + }); + assertThat(e.getMessage(), is("The Claim 'anotherClaimName' is not present in the JWT.")); + assertThat(e.getClaimName(), is("anotherClaimName")); } } diff --git a/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java b/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java deleted file mode 100644 index acff9b0f..00000000 --- a/lib/src/test/java/com/auth0/jwt/matchers/CustomMatchers.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.auth0.jwt.matchers; - -import com.auth0.jwt.exceptions.IncorrectClaimException; -import com.auth0.jwt.exceptions.MissingClaimException; -import com.auth0.jwt.exceptions.TokenExpiredException; -import com.auth0.jwt.interfaces.Claim; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import java.time.Instant; -import java.util.Arrays; - -public class CustomMatchers { - public static Matcher hasMissingClaimName(final String claimName) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("MissingClaimException with claim name: "+claimName); - } - - @Override - protected boolean matchesSafely(MissingClaimException item) { - return item.getClaimName().equals(claimName); - } - }; - } - - public static Matcher hasTokenExpiredOn(final Instant instant) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("TokenExpiredException with expired time as: "+instant.getEpochSecond()); - } - - @Override - protected boolean matchesSafely(TokenExpiredException item) { - return item.getExpiredOn().equals(instant); - } - }; - } - - public static Matcher hasClaimName(final String claimName) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim name: "+claimName); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimName().equals(claimName); - } - }; - } - - public static Matcher hasClaimValue(final Object value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimValue().as(clazz).equals(value); - } - }; - } - - public static Matcher hasClaimInstant(final Instant value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return item.getClaimValue().as(clazz).equals(value); - } - }; - } - - public static Matcher hasClaimValueArray(final Object value, final Class clazz) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim : "+value); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - return Arrays.equals((Object[]) item.getClaimValue().as(clazz), (Object[])value); - } - }; - } - - public static Matcher hasNullClaim() { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("IncorrectClaimException with claim as null"); - } - - @Override - protected boolean matchesSafely(IncorrectClaimException item) { - boolean a = item.getClaimValue().isNull(); - String b = item.getClaimValue().toString(); - return item.getClaimValue().isNull(); - } - }; - } -} From af04b229327f863cdde7c41c77c076e39a6b3742 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 13 Apr 2022 15:05:02 +0530 Subject: [PATCH 245/355] [SDK-3231] Added support for multiple checks on a single claim (#573) * Added support for multiple checks on a single claim * Fix codecov CI failure * Allow pseudo comparison for codecov * Remove newly added parameters and check codecov disabled * Reenabled changes check in codecov * Trigger Build * Refactor ExpectedCheckHolder from interfaces to impl package * Refactored code to improve coverage report --- .../main/java/com/auth0/jwt/JWTVerifier.java | 153 ++++++----- .../auth0/jwt/impl/ExpectedCheckHolder.java | 25 ++ .../java/com/auth0/jwt/JWTVerifierTest.java | 241 ++++++++++++------ 3 files changed, 282 insertions(+), 137 deletions(-) create mode 100644 lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index ba19feb9..801dac24 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -6,6 +6,7 @@ import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.impl.ExpectedCheckHolder; import com.auth0.jwt.interfaces.Verification; import java.time.Clock; @@ -25,12 +26,12 @@ */ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { private final Algorithm algorithm; - final Map> expectedChecks; + final List expectedChecks; private final JWTParser parser; - JWTVerifier(Algorithm algorithm, Map> expectedChecks) { + JWTVerifier(Algorithm algorithm, List expectedChecks) { this.algorithm = algorithm; - this.expectedChecks = Collections.unmodifiableMap(expectedChecks); + this.expectedChecks = Collections.unmodifiableList(expectedChecks); this.parser = new JWTParser(); } @@ -50,7 +51,7 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { */ public static class BaseVerification implements Verification { private final Algorithm algorithm; - private final Map> expectedChecks; + private final List expectedChecks; private long defaultLeeway; private final Map customLeeways; private boolean ignoreIssuedAt; @@ -62,7 +63,7 @@ public static class BaseVerification implements Verification { } this.algorithm = algorithm; - this.expectedChecks = new LinkedHashMap<>(); + this.expectedChecks = new ArrayList<>(); this.customLeeways = new HashMap<>(); this.defaultLeeway = 0; } @@ -70,7 +71,10 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - checkIfNeedToRemove(PublicClaims.ISSUER, value, ((claim, decodedJWT) -> { + addCheck(PublicClaims.ISSUER, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); @@ -82,23 +86,40 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - checkIfNeedToRemove(PublicClaims.SUBJECT, subject, (claim, decodedJWT) -> subject.equals(claim.asString())); + addCheck(PublicClaims.SUBJECT, (claim, decodedJWT) -> + verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, true))); + addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } + if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { + throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", + PublicClaims.AUDIENCE, claim); + } + return true; + })); return this; } @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - checkIfNeedToRemove(PublicClaims.AUDIENCE, value, ((claim, decodedJWT) -> - assertValidAudienceClaim(claim, decodedJWT.getAudience(), value, false))); + addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + if (verifyNull(claim, value)) { + return true; + } + if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { + throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", + PublicClaims.AUDIENCE, claim); + } + return true; + })); return this; } @@ -138,14 +159,16 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - checkIfNeedToRemove(PublicClaims.JWT_ID, jwtId, ((claim, decodedJWT) -> jwtId.equals(claim.asString()))); + addCheck(PublicClaims.JWT_ID, ((claim, decodedJWT) -> + verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @Override public Verification withClaimPresence(String name) throws IllegalArgumentException { assertNonNull(name); - withClaim(name, ((claim, decodedJWT) -> assertClaimPresence(name, claim))); + //since addCheck already checks presence, we just return true + withClaim(name, ((claim, decodedJWT) -> true)); return this; } @@ -159,35 +182,40 @@ public Verification withNullClaim(String name) throws IllegalArgumentException { @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asBoolean()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asBoolean()))); return this; } @Override public Verification withClaim(String name, Integer value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asInt()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asInt()))); return this; } @Override public Verification withClaim(String name, Long value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asLong()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asLong()))); return this; } @Override public Verification withClaim(String name, Double value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asDouble()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asDouble()))); return this; } @Override public Verification withClaim(String name, String value) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, value, ((claim, decodedJWT) -> value.equals(claim.asString()))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value) + || value.equals(claim.asString()))); return this; } @@ -201,8 +229,9 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument assertNonNull(name); // Since date-time claims are serialized as epoch seconds, // we need to compare them with only seconds-granularity - checkIfNeedToRemove(name, value, - ((claim, decodedJWT) -> value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); + addCheck(name, + ((claim, decodedJWT) -> verifyNull(claim, value) + || value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant()))); return this; } @@ -210,28 +239,32 @@ public Verification withClaim(String name, Instant value) throws IllegalArgument public Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, predicate, predicate); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, predicate) + || predicate.test(claim, decodedJWT))); return this; } @Override public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @Override public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException { assertNonNull(name); - checkIfNeedToRemove(name, items, ((claim, decodedJWT) -> assertValidCollectionClaim(claim, items))); + addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items) + || assertValidCollectionClaim(claim, items))); return this; } @@ -268,13 +301,13 @@ private void addMandatoryClaimChecks() { long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - expectedChecks.put(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true)); - expectedChecks.put(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false)); + expectedChecks.add(constructExpectedCheck(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.put(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false)); + expectedChecks.add(constructExpectedCheck(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } @@ -294,8 +327,7 @@ private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimVa } } } else { - claimArr = claim.isNull() || claim.isMissing() - ? Collections.emptyList() : Arrays.asList(claim.as(Object[].class)); + claimArr = Arrays.asList(claim.as(Object[].class)); } List valueArr = Arrays.asList(expectedClaimValue); return claimArr.containsAll(valueArr); @@ -329,24 +361,12 @@ private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) } private boolean assertValidAudienceClaim( - Claim claim, List audience, List values, boolean shouldContainAll ) { - if (audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))) { - throw new IncorrectClaimException( - "The Claim 'aud' value doesn't contain the required audience.", PublicClaims.AUDIENCE, claim); - } - return true; - } - - private boolean assertClaimPresence(String name, Claim claim) { - if (claim.isMissing()) { - throw new MissingClaimException(name); - } - return true; + return !(audience == null || (shouldContainAll && !audience.containsAll(values)) + || (!shouldContainAll && Collections.disjoint(audience, values))); } private void assertPositive(long leeway) { @@ -361,13 +381,31 @@ private void assertNonNull(String name) { } } - private void checkIfNeedToRemove(String name, Object value, BiPredicate predicate) { - if (value == null) { - expectedChecks.remove(name); - return; - } - expectedChecks.put(name, (claim, decodedJWT) -> assertClaimPresence(name, claim) - && predicate.test(claim, decodedJWT)); + private void addCheck(String name, BiPredicate predicate) { + expectedChecks.add(constructExpectedCheck(name, (claim, decodedJWT) -> { + if (claim.isMissing()) { + throw new MissingClaimException(name); + } + return predicate.test(claim, decodedJWT); + })); + } + + private ExpectedCheckHolder constructExpectedCheck(String claimName, BiPredicate check) { + return new ExpectedCheckHolder() { + @Override + public String getClaimName() { + return claimName; + } + + @Override + public boolean verify(Claim claim, DecodedJWT decodedJWT) { + return check.test(claim, decodedJWT); + } + }; + } + + private boolean verifyNull(Claim claim, Object value) { + return value == null && claim.isNull(); } private boolean isNullOrEmpty(String[] args) { @@ -431,15 +469,14 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws } } - private void verifyClaims(DecodedJWT jwt, Map> claims) + private void verifyClaims(DecodedJWT jwt, List expectedChecks) throws TokenExpiredException, InvalidClaimException { - for (Map.Entry> entry : claims.entrySet()) { + for (ExpectedCheckHolder expectedCheck : expectedChecks) { boolean isValid; - String claimName = entry.getKey(); - BiPredicate expectedCheck = entry.getValue(); + String claimName = expectedCheck.getClaimName(); Claim claim = jwt.getClaim(claimName); - isValid = expectedCheck.test(claim, jwt); + isValid = expectedCheck.verify(claim, jwt); if (!isValid) { throw new IncorrectClaimException( diff --git a/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java b/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java new file mode 100644 index 00000000..6737031c --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/impl/ExpectedCheckHolder.java @@ -0,0 +1,25 @@ +package com.auth0.jwt.impl; + +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +/** + * This holds the checks that are run to verify a JWT. + */ +public interface ExpectedCheckHolder { + /** + * The claim name that will be checked. + * + * @return the claim name + */ + String getClaimName(); + + /** + * The verification that will be run. + * + * @param claim the claim for which verification is done + * @param decodedJWT the JWT on which verification is done + * @return whether the verification passed or not + */ + boolean verify(Claim claim, DecodedJWT decodedJWT); +} diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 9dcb2cb1..72e5656f 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -56,8 +56,18 @@ public void shouldValidateIssuer() { .withIssuer("auth0") .build() .verify(token); - assertThat(jwt, is(notNullValue())); + + // "iss": ["auth0", "okta"] + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, ()-> { + String token1 = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer((String[]) null) + .build() + .verify(token1); + }); + + assertThat(e.getClaimName(), is("iss")); } @Test @@ -179,7 +189,7 @@ public void shouldAllowWithAnyOfAudienceVerificationToOverrideWithAudience() { assertThat(exception, is(instanceOf(IncorrectClaimException.class))); assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - DecodedJWT jwt = verification.withAnyOfAudience("Mark", "Jim").build().verify(token); + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Mark", "Jim").build().verify(token); assertThat(jwt, is(notNullValue())); } @@ -201,7 +211,7 @@ public void shouldAllowWithAudienceVerificationToOverrideWithAnyOfAudience() { assertThat(exception, is(instanceOf(IncorrectClaimException.class))); assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - DecodedJWT jwt = verification.withAudience("Mark").build().verify(token); + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark").build().verify(token); assertThat(jwt, is(notNullValue())); } @@ -303,75 +313,15 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { } @Test - public void shouldRemoveAudienceWhenPassingNullReference() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - verifier = JWTVerifier.init(algorithm) + public void shouldNotReplaceWhenMultipleChecksAreAdded() { + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withAudience((String[]) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - verifier = JWTVerifier.init(algorithm) .withAudience() - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey(PublicClaims.AUDIENCE))); - - String emptyAud = " "; - verifier = JWTVerifier.init(algorithm) - .withAudience(emptyAud) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - } - - @Test - public void shouldRemoveAudienceWhenPassingNull() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAudience("John") - .withAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - - verifier = JWTVerifier.init(algorithm) - .withAudience("John") - .withAudience((String[]) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - } - - @Test - public void shouldRemoveAudienceWhenPassingNullWithAnyAudience() { - Algorithm algorithm = mock(Algorithm.class); - JWTVerifier verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience("John") - .withAnyOfAudience((String) null) - .build(); - - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); - - verifier = JWTVerifier.init(algorithm) - .withAnyOfAudience("John") .withAnyOfAudience((String[]) null) + .withAnyOfAudience() .build(); - assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("aud"))); + assertThat(verifier.expectedChecks.size(), is(7)); //3 extra mandatory checks exp, nbf, iat } @Test @@ -561,14 +511,14 @@ public void shouldValidateCustomClaimOfTypeDate() { } @Test - public void shouldRemoveCustomClaimOfTypeDateWhenNull() { + public void shouldNotRemoveCustomClaimOfTypeDateWhenNull() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("name", new Date()) .withClaim("name", (Date) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test @@ -900,7 +850,7 @@ public void shouldThrowOnInvalidJWTId() { } @Test - public void shouldRemoveClaimWhenPassingNull() { + public void shouldNotRemoveClaimWhenPassingNull() { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withIssuer("iss") @@ -908,7 +858,7 @@ public void shouldRemoveClaimWhenPassingNull() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); verifier = JWTVerifier.init(algorithm) .withIssuer("iss") @@ -916,32 +866,32 @@ public void shouldRemoveClaimWhenPassingNull() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test - public void shouldRemoveIssuerWhenPassingNullReference() { + public void shouldNotRemoveIssuerWhenPassingNullReference() { Algorithm algorithm = mock(Algorithm.class); JWTVerifier verifier = JWTVerifier.init(algorithm) .withIssuer((String) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); verifier = JWTVerifier.init(algorithm) .withIssuer((String[]) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); verifier = JWTVerifier.init(algorithm) .withIssuer() .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("iss"))); + assertThat(verifier.expectedChecks.size(), is(4)); String emptyIss = " "; verifier = JWTVerifier.init(algorithm) @@ -1111,7 +1061,6 @@ public void shouldSuccessfullyVerifyClaimWithPredicate() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("claimName", (claim, decodedJWT) -> "claimValue".equals(claim.asString())) - .withClaim(PublicClaims.ISSUED_AT, ((claim, decodedJWT) -> false)) .build(); DecodedJWT decodedJWT = verifier.verify(jwt); @@ -1136,14 +1085,14 @@ public void shouldThrowWhenPredicateReturnsFalse() { } @Test - public void shouldRemovePredicateCheckForNull() { + public void shouldNotRemovePredicateCheckForNull() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString())) .withClaim("claimName", (BiPredicate) null) .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verifier.expectedChecks, not(hasKey("claimName"))); + assertThat(verifier.expectedChecks.size(), is(5)); } @Test @@ -1192,4 +1141,138 @@ public void shouldThrowWhenNullClaimIsMissing() { assertThat(e.getMessage(), is("The Claim 'anotherClaimName' is not present in the JWT.")); assertThat(e.getClaimName(), is("anotherClaimName")); } + + @Test + public void shouldCheckForNullValuesForSubject() { + // sub = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGx9.y5brmQQ05OYwVvlTg83njUrz6tfpdyWNh17LHU6DxmI"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withSubject(null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInIssuer() { + // iss = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withIssuer((String) null) + .withIssuer((String[]) null) + .withIssuer() + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInJwtId() { + // jti = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGx9.z_MDyl8uPGH0q0jeB54wbYt3bwKXamU_3MO8LofGvZs"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withJWTId(null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForNullValuesInCustomClaims() { + // jti = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOm51bGx9.inAuN3Q9UZ6WgbB63O43B1ero2MTqnfzzumr_5qYIls"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", (Boolean) null) + .withClaim("custom", (Integer) null) + .withClaim("custom", (Long) null) + .withClaim("custom", (Double) null) + .withClaim("custom", (String) null) + .withClaim("custom", (Date) null) + .withClaim("custom", (Instant) null) + .withClaim("custom", (BiPredicate) null) + .withArrayClaim("custom", (String[]) null) + .withArrayClaim("custom", (Integer[]) null) + .withArrayClaim("custom", (Long[]) null) + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + + @Test + public void shouldCheckForNullValuesForAudience() { + // aud = null + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAudience((String[]) null) + .withAudience((String) null) + .withAudience() + .withAnyOfAudience((String[]) null) + .withAnyOfAudience((String) null) + .withAnyOfAudience() + .build() + .verify(token); + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldCheckForClaimPresenceEvenForNormalClaimChecks() { + MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", true) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongLongClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOjF9.00btiK0sv8pQ2T-hOr9GC5x2osi7--Bsk4pS5cTikqQ"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaim("custom", 2L) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + assertThat(e.getClaimValue().asLong(), is(1L)); + } + + @Test + public void shouldCheckForWrongLongArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", 2L) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongStringArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", "2L") + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } + + @Test + public void shouldCheckForWrongIntegerArrayClaim() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withArrayClaim("custom", 2) + .build() + .verify(token); + }); + assertThat(e.getClaimName(), is("custom")); + } } From 05c925edc68eb072e6910055891d1317be2325a2 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 09:58:21 -0500 Subject: [PATCH 246/355] [SDK-3226] Expose claim header constants --- lib/src/main/java/HeaderParams.java | 29 +++++++++++ .../main/java/com/auth0/jwt/JWTCreator.java | 28 +++++----- .../main/java/com/auth0/jwt/JWTVerifier.java | 43 ++++++++------- .../java/com/auth0/jwt/StandardClaims.java | 48 +++++++++++++++++ .../auth0/jwt/impl/HeaderDeserializer.java | 9 ++-- .../auth0/jwt/impl/PayloadDeserializer.java | 15 +++--- .../com/auth0/jwt/impl/PayloadSerializer.java | 3 +- .../java/com/auth0/jwt/impl/PublicClaims.java | 23 -------- .../java/com/auth0/jwt/JWTCreatorTest.java | 21 ++++---- .../java/com/auth0/jwt/JWTDecoderTest.java | 4 +- lib/src/test/java/com/auth0/jwt/JWTTest.java | 6 +-- .../java/com/auth0/jwt/JWTVerifierTest.java | 52 +++++++++---------- 12 files changed, 166 insertions(+), 115 deletions(-) create mode 100644 lib/src/main/java/HeaderParams.java create mode 100644 lib/src/main/java/com/auth0/jwt/StandardClaims.java delete mode 100644 lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java diff --git a/lib/src/main/java/HeaderParams.java b/lib/src/main/java/HeaderParams.java new file mode 100644 index 00000000..e15877a2 --- /dev/null +++ b/lib/src/main/java/HeaderParams.java @@ -0,0 +1,29 @@ +package com.auth0.jwt; + +/** + * Contains constants representing the JWT header parameter names. + */ +public final class HeaderParams { + + private HeaderParams() {} + + /** + * The algorithm used to sign a JWT. + */ + public static String ALGORITHM = "alg"; + + /** + * The content type of a JWT. + */ + public static String CONTENT_TYPE = "cty"; + + /** + * The media type of a JWT. + */ + public static String TYPE = "typ"; + + /** + * The key ID of a JWT used to specify the key for signature validation. + */ + public static String KEY_ID = "kid"; +} diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index e1159ae4..f0299d22 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -104,7 +104,7 @@ public Builder withHeader(Map headerClaims) { * @return this same Builder instance. */ public Builder withKeyId(String keyId) { - this.headerClaims.put(PublicClaims.KEY_ID, keyId); + this.headerClaims.put(HeaderParams.KEY_ID, keyId); return this; } @@ -115,7 +115,7 @@ public Builder withKeyId(String keyId) { * @return this same Builder instance. */ public Builder withIssuer(String issuer) { - addClaim(PublicClaims.ISSUER, issuer); + addClaim(StandardClaims.ISSUER, issuer); return this; } @@ -126,7 +126,7 @@ public Builder withIssuer(String issuer) { * @return this same Builder instance. */ public Builder withSubject(String subject) { - addClaim(PublicClaims.SUBJECT, subject); + addClaim(StandardClaims.SUBJECT, subject); return this; } @@ -137,7 +137,7 @@ public Builder withSubject(String subject) { * @return this same Builder instance. */ public Builder withAudience(String... audience) { - addClaim(PublicClaims.AUDIENCE, audience); + addClaim(StandardClaims.AUDIENCE, audience); return this; } @@ -149,7 +149,7 @@ public Builder withAudience(String... audience) { * @return this same Builder instance. */ public Builder withExpiresAt(Date expiresAt) { - addClaim(PublicClaims.EXPIRES_AT, expiresAt); + addClaim(StandardClaims.EXPIRES_AT, expiresAt); return this; } @@ -161,7 +161,7 @@ public Builder withExpiresAt(Date expiresAt) { * @return this same Builder instance. */ public Builder withExpiresAt(Instant expiresAt) { - addClaim(PublicClaims.EXPIRES_AT, expiresAt); + addClaim(StandardClaims.EXPIRES_AT, expiresAt); return this; } @@ -173,7 +173,7 @@ public Builder withExpiresAt(Instant expiresAt) { * @return this same Builder instance. */ public Builder withNotBefore(Date notBefore) { - addClaim(PublicClaims.NOT_BEFORE, notBefore); + addClaim(StandardClaims.NOT_BEFORE, notBefore); return this; } @@ -185,7 +185,7 @@ public Builder withNotBefore(Date notBefore) { * @return this same Builder instance. */ public Builder withNotBefore(Instant notBefore) { - addClaim(PublicClaims.NOT_BEFORE, notBefore); + addClaim(StandardClaims.NOT_BEFORE, notBefore); return this; } @@ -197,7 +197,7 @@ public Builder withNotBefore(Instant notBefore) { * @return this same Builder instance. */ public Builder withIssuedAt(Date issuedAt) { - addClaim(PublicClaims.ISSUED_AT, issuedAt); + addClaim(StandardClaims.ISSUED_AT, issuedAt); return this; } @@ -209,7 +209,7 @@ public Builder withIssuedAt(Date issuedAt) { * @return this same Builder instance. */ public Builder withIssuedAt(Instant issuedAt) { - addClaim(PublicClaims.ISSUED_AT, issuedAt); + addClaim(StandardClaims.ISSUED_AT, issuedAt); return this; } @@ -220,7 +220,7 @@ public Builder withIssuedAt(Instant issuedAt) { * @return this same Builder instance. */ public Builder withJWTId(String jwtId) { - addClaim(PublicClaims.JWT_ID, jwtId); + addClaim(StandardClaims.JWT_ID, jwtId); return this; } @@ -543,9 +543,9 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea if (algorithm == null) { throw new IllegalArgumentException("The Algorithm cannot be null."); } - headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName()); - if (!headerClaims.containsKey(PublicClaims.TYPE)) { - headerClaims.put(PublicClaims.TYPE, "JWT"); + headerClaims.put(HeaderParams.ALGORITHM, algorithm.getName()); + if (!headerClaims.containsKey(HeaderParams.TYPE)) { + headerClaims.put(HeaderParams.TYPE, "JWT"); } String signingKeyId = algorithm.getSigningKeyId(); if (signingKeyId != null) { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 801dac24..223de7f5 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,7 +3,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.impl.ExpectedCheckHolder; @@ -71,13 +70,13 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - addCheck(PublicClaims.ISSUER, ((claim, decodedJWT) -> { + addCheck(StandardClaims.ISSUER, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( - "The Claim 'iss' value doesn't match the required issuer.", PublicClaims.ISSUER, claim); + "The Claim 'iss' value doesn't match the required issuer.", StandardClaims.ISSUER, claim); } return true; })); @@ -86,7 +85,7 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - addCheck(PublicClaims.SUBJECT, (claim, decodedJWT) -> + addCheck(StandardClaims.SUBJECT, (claim, decodedJWT) -> verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @@ -94,13 +93,13 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - PublicClaims.AUDIENCE, claim); + StandardClaims.AUDIENCE, claim); } return true; })); @@ -110,13 +109,13 @@ public Verification withAudience(String... audience) { @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(PublicClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - PublicClaims.AUDIENCE, claim); + StandardClaims.AUDIENCE, claim); } return true; })); @@ -133,21 +132,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.EXPIRES_AT, leeway); + customLeeways.put(StandardClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.NOT_BEFORE, leeway); + customLeeways.put(StandardClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(PublicClaims.ISSUED_AT, leeway); + customLeeways.put(StandardClaims.ISSUED_AT, leeway); return this; } @@ -159,7 +158,7 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - addCheck(PublicClaims.JWT_ID, ((claim, decodedJWT) -> + addCheck(StandardClaims.JWT_ID, ((claim, decodedJWT) -> verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @@ -297,17 +296,17 @@ public long getLeewayFor(String name) { } private void addMandatoryClaimChecks() { - long expiresAtLeeway = getLeewayFor(PublicClaims.EXPIRES_AT); - long notBeforeLeeway = getLeewayFor(PublicClaims.NOT_BEFORE); - long issuedAtLeeway = getLeewayFor(PublicClaims.ISSUED_AT); - - expectedChecks.add(constructExpectedCheck(PublicClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); - expectedChecks.add(constructExpectedCheck(PublicClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); + long expiresAtLeeway = getLeewayFor(StandardClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(StandardClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(StandardClaims.ISSUED_AT); + + expectedChecks.add(constructExpectedCheck(StandardClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(StandardClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.add(constructExpectedCheck(PublicClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(PublicClaims.ISSUED_AT, claim, issuedAtLeeway, false))); + expectedChecks.add(constructExpectedCheck(StandardClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(StandardClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } diff --git a/lib/src/main/java/com/auth0/jwt/StandardClaims.java b/lib/src/main/java/com/auth0/jwt/StandardClaims.java new file mode 100644 index 00000000..4fe82d64 --- /dev/null +++ b/lib/src/main/java/com/auth0/jwt/StandardClaims.java @@ -0,0 +1,48 @@ +package com.auth0.jwt; + +/** + * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of + * RFC 7529 + */ +public final class StandardClaims { + + private StandardClaims() { + } + + /** + * The "iss" (issuer) claim identifies the principal that issued the JWT. + */ + public static String ISSUER = "iss"; + + /** + * The "sub" (subject) claim identifies the principal that is the subject of the JWT. + */ + public static String SUBJECT = "sub"; + + /** + * The "aud" (audience) claim identifies the recipients that the JWT is intended for. + */ + public static String AUDIENCE = "aud"; + + /** + * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be + * accepted for processing. + */ + public static String EXPIRES_AT = "exp"; + + /** + * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. + */ + public static String NOT_BEFORE = "nbf"; + + /** + * The "iat" (issued at) claim identifies the time at which the JWT was issued. + */ + public static String ISSUED_AT = "iat"; + + /** + * The "jti" (JWT ID) claim provides a unique identifier for the JWT. + */ + public static String JWT_ID = "jti"; + +} diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 8a6cbcf8..9293fd4d 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.HeaderParams; import com.auth0.jwt.exceptions.JWTDecodeException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; @@ -40,10 +41,10 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws throw new JWTDecodeException("Parsing the Header's JSON resulted on a Null map"); } - String algorithm = getString(tree, PublicClaims.ALGORITHM); - String type = getString(tree, PublicClaims.TYPE); - String contentType = getString(tree, PublicClaims.CONTENT_TYPE); - String keyId = getString(tree, PublicClaims.KEY_ID); + String algorithm = getString(tree, HeaderParams.ALGORITHM); + String type = getString(tree, HeaderParams.TYPE); + String contentType = getString(tree, HeaderParams.CONTENT_TYPE); + String keyId = getString(tree, HeaderParams.KEY_ID); return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 09009267..4d058826 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.StandardClaims; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; @@ -43,13 +44,13 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE throw new JWTDecodeException("Parsing the Payload's JSON resulted on a Null map"); } - String issuer = getString(tree, PublicClaims.ISSUER); - String subject = getString(tree, PublicClaims.SUBJECT); - List audience = getStringOrArray(tree, PublicClaims.AUDIENCE); - Instant expiresAt = getInstantFromSeconds(tree, PublicClaims.EXPIRES_AT); - Instant notBefore = getInstantFromSeconds(tree, PublicClaims.NOT_BEFORE); - Instant issuedAt = getInstantFromSeconds(tree, PublicClaims.ISSUED_AT); - String jwtId = getString(tree, PublicClaims.JWT_ID); + String issuer = getString(tree, StandardClaims.ISSUER); + String subject = getString(tree, StandardClaims.SUBJECT); + List audience = getStringOrArray(tree, StandardClaims.AUDIENCE); + Instant expiresAt = getInstantFromSeconds(tree, StandardClaims.EXPIRES_AT); + Instant notBefore = getInstantFromSeconds(tree, StandardClaims.NOT_BEFORE); + Instant issuedAt = getInstantFromSeconds(tree, StandardClaims.ISSUED_AT); + String jwtId = getString(tree, StandardClaims.JWT_ID); return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index dc138c08..1d2ada43 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -1,5 +1,6 @@ package com.auth0.jwt.impl; +import com.auth0.jwt.StandardClaims; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; @@ -22,7 +23,7 @@ public PayloadSerializer() { @Override protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { - if (PublicClaims.AUDIENCE.equals(entry.getKey())) { + if (StandardClaims.AUDIENCE.equals(entry.getKey())) { writeAudience(gen, entry); } else { super.writeClaim(entry, gen); diff --git a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java b/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java deleted file mode 100644 index ea166b0d..00000000 --- a/lib/src/main/java/com/auth0/jwt/impl/PublicClaims.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.auth0.jwt.impl; - -/** - * Contains the claim name for all Public claims. - */ -public interface PublicClaims { - - //Header - String ALGORITHM = "alg"; - String CONTENT_TYPE = "cty"; - String TYPE = "typ"; - String KEY_ID = "kid"; - - //Payload - String ISSUER = "iss"; - String SUBJECT = "sub"; - String EXPIRES_AT = "exp"; - String NOT_BEFORE = "nbf"; - String ISSUED_AT = "iat"; - String JWT_ID = "jti"; - String AUDIENCE = "aud"; - -} diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 3cccbf7b..b28035f8 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -1,7 +1,6 @@ package com.auth0.jwt; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; import com.fasterxml.jackson.databind.ObjectMapper; @@ -92,7 +91,7 @@ public void shouldReturnBuilderIfNullMapIsProvided() { @Test public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, "xyz"); + header.put(HeaderParams.KEY_ID, "xyz"); String signed = JWTCreator.init() .withKeyId("abc") @@ -102,13 +101,13 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } @Test public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, "xyz"); + header.put(HeaderParams.KEY_ID, "xyz"); String signed = JWTCreator.init() .withHeader(header) @@ -118,13 +117,13 @@ public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "abc")); + assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "abc")); } @Test public void shouldRemoveHeaderIfTheValueIsNull() { Map header = new HashMap<>(); - header.put(PublicClaims.KEY_ID, null); + header.put(HeaderParams.KEY_ID, null); header.put("test2", "isSet"); String signed = JWTCreator.init() @@ -135,7 +134,7 @@ public void shouldRemoveHeaderIfTheValueIsNull() { assertThat(signed, is(notNullValue())); String[] parts = signed.split("\\."); String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); - assertThat(headerJson, JsonMatcher.isNotPresent(PublicClaims.KEY_ID)); + assertThat(headerJson, JsonMatcher.isNotPresent(HeaderParams.KEY_ID)); assertThat(headerJson, JsonMatcher.hasEntry("test2", "isSet")); } @@ -728,7 +727,7 @@ public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { @Test public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKey() { Map payload = new HashMap<>(); - payload.put(PublicClaims.KEY_ID, "xyz"); + payload.put(HeaderParams.KEY_ID, "xyz"); String jwt = JWTCreator.init() .withKeyId("abc") @@ -738,13 +737,13 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.KEY_ID, "xyz")); + assertThat(payloadJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } @Test public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { Map payload = new HashMap<>(); - payload.put(PublicClaims.ISSUER, "xyz"); + payload.put(StandardClaims.ISSUER, "xyz"); String jwt = JWTCreator.init() .withPayload(payload) @@ -754,7 +753,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(PublicClaims.ISSUER, "abc")); + assertThat(payloadJson, JsonMatcher.hasEntry(StandardClaims.ISSUER, "abc")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 5ea2b4ef..82dc895c 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -121,7 +121,7 @@ public void shouldGetSignature() { assertThat(jwt.getSignature(), is("XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ")); } - // Public PublicClaims + // Standard Claims @Test public void shouldGetIssuer() { @@ -208,7 +208,7 @@ public void shouldGetAlgorithm() { assertThat(jwt.getAlgorithm(), is("HS256")); } - //Private PublicClaims + // Private Claims @Test public void shouldGetMissingClaimIfClaimDoesNotExist() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index 958e89fb..b9f56a2e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -10,8 +10,6 @@ import java.nio.charset.StandardCharsets; import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.time.Clock; import java.time.Instant; @@ -19,8 +17,8 @@ import java.util.Base64; import java.util.Date; -import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; public class JWTTest { @@ -199,7 +197,7 @@ public void shouldAcceptECDSA512Algorithm() throws Exception { } - // Public Claims + // Standard Claims @Test public void shouldGetAlgorithm() { diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 72e5656f..d2715262 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -2,7 +2,6 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; -import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; @@ -14,7 +13,6 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.function.BiPredicate; @@ -92,7 +90,7 @@ public void shouldThrowOnInvalidIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); assertThat(e.getClaimValue().asString(), is("auth0")); } @@ -106,7 +104,7 @@ public void shouldThrowOnNullIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUER)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -146,7 +144,7 @@ public void shouldThrowOnInvalidSubject() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); - assertThat(e.getClaimName(), is(PublicClaims.SUBJECT)); + assertThat(e.getClaimName(), is(StandardClaims.SUBJECT)); assertThat(e.getClaimValue().asString(), is("1234567890")); } @@ -250,7 +248,7 @@ public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { .verify(tokenArr); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -265,7 +263,7 @@ public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -280,7 +278,7 @@ public void shouldThrowWhenAudienceClaimIsNull() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -308,7 +306,7 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(PublicClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @@ -585,9 +583,9 @@ public void shouldAddDefaultLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(0L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(0L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(0L)); } @Test @@ -599,9 +597,9 @@ public void shouldAddCustomLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -614,9 +612,9 @@ public void shouldOverrideDefaultIssuedAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(9999L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -629,9 +627,9 @@ public void shouldOverrideDefaultExpiresAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(9999L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); } @Test @@ -644,9 +642,9 @@ public void shouldOverrideDefaultNotBeforeLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(PublicClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(PublicClaims.NOT_BEFORE), is(9999L)); + assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(9999L)); } @Test @@ -727,7 +725,7 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.NOT_BEFORE)); + assertThat(e.getClaimName(), is(StandardClaims.NOT_BEFORE)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -762,7 +760,7 @@ public void shouldThrowOnFutureIssuedAt() { assertThat(jwt, is(notNullValue())); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -787,7 +785,7 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(PublicClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } From 5da2806b592daeef57b37e5d5461de6d0ef2bbe9 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 12:55:40 -0500 Subject: [PATCH 247/355] fix packaging --- lib/src/main/java/{ => com/auth0/jwt}/HeaderParams.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/src/main/java/{ => com/auth0/jwt}/HeaderParams.java (100%) diff --git a/lib/src/main/java/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java similarity index 100% rename from lib/src/main/java/HeaderParams.java rename to lib/src/main/java/com/auth0/jwt/HeaderParams.java From 035f323dc4e0c4654c661c050843f521028aec21 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 13 Apr 2022 13:01:04 -0500 Subject: [PATCH 248/355] Update class names --- .../main/java/com/auth0/jwt/JWTCreator.java | 20 ++++---- .../main/java/com/auth0/jwt/JWTVerifier.java | 42 ++++++++-------- ...ndardClaims.java => RegisteredClaims.java} | 4 +- .../auth0/jwt/impl/PayloadDeserializer.java | 16 +++--- .../com/auth0/jwt/impl/PayloadSerializer.java | 4 +- .../java/com/auth0/jwt/JWTCreatorTest.java | 4 +- .../java/com/auth0/jwt/JWTVerifierTest.java | 50 +++++++++---------- 7 files changed, 70 insertions(+), 70 deletions(-) rename lib/src/main/java/com/auth0/jwt/{StandardClaims.java => RegisteredClaims.java} (94%) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f0299d22..cab474f7 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -115,7 +115,7 @@ public Builder withKeyId(String keyId) { * @return this same Builder instance. */ public Builder withIssuer(String issuer) { - addClaim(StandardClaims.ISSUER, issuer); + addClaim(RegisteredClaims.ISSUER, issuer); return this; } @@ -126,7 +126,7 @@ public Builder withIssuer(String issuer) { * @return this same Builder instance. */ public Builder withSubject(String subject) { - addClaim(StandardClaims.SUBJECT, subject); + addClaim(RegisteredClaims.SUBJECT, subject); return this; } @@ -137,7 +137,7 @@ public Builder withSubject(String subject) { * @return this same Builder instance. */ public Builder withAudience(String... audience) { - addClaim(StandardClaims.AUDIENCE, audience); + addClaim(RegisteredClaims.AUDIENCE, audience); return this; } @@ -149,7 +149,7 @@ public Builder withAudience(String... audience) { * @return this same Builder instance. */ public Builder withExpiresAt(Date expiresAt) { - addClaim(StandardClaims.EXPIRES_AT, expiresAt); + addClaim(RegisteredClaims.EXPIRES_AT, expiresAt); return this; } @@ -161,7 +161,7 @@ public Builder withExpiresAt(Date expiresAt) { * @return this same Builder instance. */ public Builder withExpiresAt(Instant expiresAt) { - addClaim(StandardClaims.EXPIRES_AT, expiresAt); + addClaim(RegisteredClaims.EXPIRES_AT, expiresAt); return this; } @@ -173,7 +173,7 @@ public Builder withExpiresAt(Instant expiresAt) { * @return this same Builder instance. */ public Builder withNotBefore(Date notBefore) { - addClaim(StandardClaims.NOT_BEFORE, notBefore); + addClaim(RegisteredClaims.NOT_BEFORE, notBefore); return this; } @@ -185,7 +185,7 @@ public Builder withNotBefore(Date notBefore) { * @return this same Builder instance. */ public Builder withNotBefore(Instant notBefore) { - addClaim(StandardClaims.NOT_BEFORE, notBefore); + addClaim(RegisteredClaims.NOT_BEFORE, notBefore); return this; } @@ -197,7 +197,7 @@ public Builder withNotBefore(Instant notBefore) { * @return this same Builder instance. */ public Builder withIssuedAt(Date issuedAt) { - addClaim(StandardClaims.ISSUED_AT, issuedAt); + addClaim(RegisteredClaims.ISSUED_AT, issuedAt); return this; } @@ -209,7 +209,7 @@ public Builder withIssuedAt(Date issuedAt) { * @return this same Builder instance. */ public Builder withIssuedAt(Instant issuedAt) { - addClaim(StandardClaims.ISSUED_AT, issuedAt); + addClaim(RegisteredClaims.ISSUED_AT, issuedAt); return this; } @@ -220,7 +220,7 @@ public Builder withIssuedAt(Instant issuedAt) { * @return this same Builder instance. */ public Builder withJWTId(String jwtId) { - addClaim(StandardClaims.JWT_ID, jwtId); + addClaim(RegisteredClaims.JWT_ID, jwtId); return this; } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 223de7f5..8b4639ac 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -70,13 +70,13 @@ public static class BaseVerification implements Verification { @Override public Verification withIssuer(String... issuer) { List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer); - addCheck(StandardClaims.ISSUER, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.ISSUER, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (value == null || !value.contains(claim.asString())) { throw new IncorrectClaimException( - "The Claim 'iss' value doesn't match the required issuer.", StandardClaims.ISSUER, claim); + "The Claim 'iss' value doesn't match the required issuer.", RegisteredClaims.ISSUER, claim); } return true; })); @@ -85,7 +85,7 @@ public Verification withIssuer(String... issuer) { @Override public Verification withSubject(String subject) { - addCheck(StandardClaims.SUBJECT, (claim, decodedJWT) -> + addCheck(RegisteredClaims.SUBJECT, (claim, decodedJWT) -> verifyNull(claim, subject) || subject.equals(claim.asString())); return this; } @@ -93,13 +93,13 @@ public Verification withSubject(String subject) { @Override public Verification withAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - StandardClaims.AUDIENCE, claim); + RegisteredClaims.AUDIENCE, claim); } return true; })); @@ -109,13 +109,13 @@ public Verification withAudience(String... audience) { @Override public Verification withAnyOfAudience(String... audience) { List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience); - addCheck(StandardClaims.AUDIENCE, ((claim, decodedJWT) -> { + addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> { if (verifyNull(claim, value)) { return true; } if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) { throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.", - StandardClaims.AUDIENCE, claim); + RegisteredClaims.AUDIENCE, claim); } return true; })); @@ -132,21 +132,21 @@ public Verification acceptLeeway(long leeway) throws IllegalArgumentException { @Override public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.EXPIRES_AT, leeway); + customLeeways.put(RegisteredClaims.EXPIRES_AT, leeway); return this; } @Override public Verification acceptNotBefore(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.NOT_BEFORE, leeway); + customLeeways.put(RegisteredClaims.NOT_BEFORE, leeway); return this; } @Override public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException { assertPositive(leeway); - customLeeways.put(StandardClaims.ISSUED_AT, leeway); + customLeeways.put(RegisteredClaims.ISSUED_AT, leeway); return this; } @@ -158,7 +158,7 @@ public Verification ignoreIssuedAt() { @Override public Verification withJWTId(String jwtId) { - addCheck(StandardClaims.JWT_ID, ((claim, decodedJWT) -> + addCheck(RegisteredClaims.JWT_ID, ((claim, decodedJWT) -> verifyNull(claim, jwtId) || jwtId.equals(claim.asString()))); return this; } @@ -296,17 +296,17 @@ public long getLeewayFor(String name) { } private void addMandatoryClaimChecks() { - long expiresAtLeeway = getLeewayFor(StandardClaims.EXPIRES_AT); - long notBeforeLeeway = getLeewayFor(StandardClaims.NOT_BEFORE); - long issuedAtLeeway = getLeewayFor(StandardClaims.ISSUED_AT); - - expectedChecks.add(constructExpectedCheck(StandardClaims.EXPIRES_AT, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); - expectedChecks.add(constructExpectedCheck(StandardClaims.NOT_BEFORE, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); + long expiresAtLeeway = getLeewayFor(RegisteredClaims.EXPIRES_AT); + long notBeforeLeeway = getLeewayFor(RegisteredClaims.NOT_BEFORE); + long issuedAtLeeway = getLeewayFor(RegisteredClaims.ISSUED_AT); + + expectedChecks.add(constructExpectedCheck(RegisteredClaims.EXPIRES_AT, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.EXPIRES_AT, claim, expiresAtLeeway, true))); + expectedChecks.add(constructExpectedCheck(RegisteredClaims.NOT_BEFORE, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.NOT_BEFORE, claim, notBeforeLeeway, false))); if (!ignoreIssuedAt) { - expectedChecks.add(constructExpectedCheck(StandardClaims.ISSUED_AT, (claim, decodedJWT) -> - assertValidInstantClaim(StandardClaims.ISSUED_AT, claim, issuedAtLeeway, false))); + expectedChecks.add(constructExpectedCheck(RegisteredClaims.ISSUED_AT, (claim, decodedJWT) -> + assertValidInstantClaim(RegisteredClaims.ISSUED_AT, claim, issuedAtLeeway, false))); } } diff --git a/lib/src/main/java/com/auth0/jwt/StandardClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java similarity index 94% rename from lib/src/main/java/com/auth0/jwt/StandardClaims.java rename to lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index 4fe82d64..d60d9fc5 100644 --- a/lib/src/main/java/com/auth0/jwt/StandardClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -4,9 +4,9 @@ * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of * RFC 7529 */ -public final class StandardClaims { +public final class RegisteredClaims { - private StandardClaims() { + private RegisteredClaims() { } /** diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 4d058826..37e70f7a 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -1,6 +1,6 @@ package com.auth0.jwt.impl; -import com.auth0.jwt.StandardClaims; +import com.auth0.jwt.RegisteredClaims; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; @@ -44,13 +44,13 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE throw new JWTDecodeException("Parsing the Payload's JSON resulted on a Null map"); } - String issuer = getString(tree, StandardClaims.ISSUER); - String subject = getString(tree, StandardClaims.SUBJECT); - List audience = getStringOrArray(tree, StandardClaims.AUDIENCE); - Instant expiresAt = getInstantFromSeconds(tree, StandardClaims.EXPIRES_AT); - Instant notBefore = getInstantFromSeconds(tree, StandardClaims.NOT_BEFORE); - Instant issuedAt = getInstantFromSeconds(tree, StandardClaims.ISSUED_AT); - String jwtId = getString(tree, StandardClaims.JWT_ID); + String issuer = getString(tree, RegisteredClaims.ISSUER); + String subject = getString(tree, RegisteredClaims.SUBJECT); + List audience = getStringOrArray(tree, RegisteredClaims.AUDIENCE); + Instant expiresAt = getInstantFromSeconds(tree, RegisteredClaims.EXPIRES_AT); + Instant notBefore = getInstantFromSeconds(tree, RegisteredClaims.NOT_BEFORE); + Instant issuedAt = getInstantFromSeconds(tree, RegisteredClaims.ISSUED_AT); + String jwtId = getString(tree, RegisteredClaims.JWT_ID); return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index 1d2ada43..c8df2bb9 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -1,6 +1,6 @@ package com.auth0.jwt.impl; -import com.auth0.jwt.StandardClaims; +import com.auth0.jwt.RegisteredClaims; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; @@ -23,7 +23,7 @@ public PayloadSerializer() { @Override protected void writeClaim(Map.Entry entry, JsonGenerator gen) throws IOException { - if (StandardClaims.AUDIENCE.equals(entry.getKey())) { + if (RegisteredClaims.AUDIENCE.equals(entry.getKey())) { writeAudience(gen, entry); } else { super.writeClaim(entry, gen); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index b28035f8..efe6b94b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -743,7 +743,7 @@ public void withPayloadShouldOverwriteExistingClaimIfPayloadMapContainsTheSameKe @Test public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { Map payload = new HashMap<>(); - payload.put(StandardClaims.ISSUER, "xyz"); + payload.put(RegisteredClaims.ISSUER, "xyz"); String jwt = JWTCreator.init() .withPayload(payload) @@ -753,7 +753,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { assertThat(jwt, is(notNullValue())); String[] parts = jwt.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); - assertThat(payloadJson, JsonMatcher.hasEntry(StandardClaims.ISSUER, "abc")); + assertThat(payloadJson, JsonMatcher.hasEntry(RegisteredClaims.ISSUER, "abc")); } @Test diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d2715262..6d8ba201 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -90,7 +90,7 @@ public void shouldThrowOnInvalidIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER)); assertThat(e.getClaimValue().asString(), is("auth0")); } @@ -104,7 +104,7 @@ public void shouldThrowOnNullIssuer() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUER)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -144,7 +144,7 @@ public void shouldThrowOnInvalidSubject() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one.")); - assertThat(e.getClaimName(), is(StandardClaims.SUBJECT)); + assertThat(e.getClaimName(), is(RegisteredClaims.SUBJECT)); assertThat(e.getClaimValue().asString(), is("1234567890")); } @@ -248,7 +248,7 @@ public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() { .verify(tokenArr); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -263,7 +263,7 @@ public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"})); } @@ -278,7 +278,7 @@ public void shouldThrowWhenAudienceClaimIsNull() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().isNull(), is(true)); } @@ -306,7 +306,7 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { .verify(token); }); assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); - assertThat(e.getClaimName(), is(StandardClaims.AUDIENCE)); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } @@ -583,9 +583,9 @@ public void shouldAddDefaultLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(0L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(0L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(0L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(0L)); } @Test @@ -597,9 +597,9 @@ public void shouldAddCustomLeewayToDateClaims() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -612,9 +612,9 @@ public void shouldOverrideDefaultIssuedAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(9999L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -627,9 +627,9 @@ public void shouldOverrideDefaultExpiresAtLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(9999L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L)); } @Test @@ -642,9 +642,9 @@ public void shouldOverrideDefaultNotBeforeLeeway() { .build(); assertThat(verifier.expectedChecks, is(notNullValue())); - assertThat(verification.getLeewayFor(StandardClaims.ISSUED_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.EXPIRES_AT), is(1234L)); - assertThat(verification.getLeewayFor(StandardClaims.NOT_BEFORE), is(9999L)); + assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L)); + assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(9999L)); } @Test @@ -725,7 +725,7 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.NOT_BEFORE)); + assertThat(e.getClaimName(), is(RegisteredClaims.NOT_BEFORE)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -760,7 +760,7 @@ public void shouldThrowOnFutureIssuedAt() { assertThat(jwt, is(notNullValue())); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } @@ -785,7 +785,7 @@ public void shouldThrowOnInvalidIssuedAtIfPresent() { .verify(token); }); assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z.")); - assertThat(e.getClaimName(), is(StandardClaims.ISSUED_AT)); + assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT)); assertThat(e.getClaimValue().asLong(), is(1477592L)); } From 537f9863b8d3297d7aa7660094a690e5909afdf8 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 14 Apr 2022 16:32:58 -0400 Subject: [PATCH 249/355] Add Semgrep to continuous integration tests This PR updates the CircleCI workflow for v4 to add Semgrep automated security testing to the continuous integration tests. --- .circleci/config.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a299cb9d..14f9652e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,11 +53,26 @@ jobs: # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" # TERM: dumb + semgrep: + docker: + - image: returntocorp/semgrep-agent:v1 + environment: + SEMGREP_REPO_NAME: "auth0/java-jwt" + SEMGREP_REPO_URL: "https://github.com/auth0/java-jwt" + steps: + - checkout + - run: + name: Run vulnerabilities tests (Semgrep) + command: | + semgrep-agent --baseline-ref v4-dev --publish-token $SEMGREP_TOKEN workflows: build-and-test: jobs: - build + - semgrep: + context: + - semgrep-env # api-diff: # jobs: -# - api-diff \ No newline at end of file +# - api-diff From 54d97c68ad2e4b57aa21dea6bc3ed6cec0c0479d Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 14 Apr 2022 16:34:15 -0400 Subject: [PATCH 250/355] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 14f9652e..f47d6efa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: - run: name: Run vulnerabilities tests (Semgrep) command: | - semgrep-agent --baseline-ref v4-dev --publish-token $SEMGREP_TOKEN + semgrep-agent --baseline-ref master --publish-token $SEMGREP_TOKEN workflows: build-and-test: From 5d8776d7e3d14f9d454404813ae0656c3a8edf2d Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 18 Apr 2022 17:04:19 -0500 Subject: [PATCH 251/355] [SDK-3244] Add Migration Guide --- MIGRATION_GUIDE.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 MIGRATION_GUIDE.md diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 00000000..1f95f323 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,84 @@ +# Migration Guide + +## Upgrading from v3.x -> v4.0 + +The version 4 release contains several improvements: + +- Support for `java.time.Instant` when creating or verifying JWTs with Numeric Date claim values. +- Improvements to JWT claim validation, including support for custom claim validation using Predicates. +- Improved exception handling when validating JWTs, to better inform of the reason for failed validation. +- Consistent handling of `null` claim values both when creating and validation JWTs. + +This guide captures the changes you should be aware of when planning and upgrading to version 4. + +### Compile or runtime breaking changes + +**Classes or methods removed:** +- The `impl` package has been removed as an export in `module-info.java`. This package contains implementation-specific code that may change at any point. +- Support for the ES256K algorithm has been removed, as it is disabled in Java 15+. The `Algorithm#ECDSA256K(ECDSAKeyProvider keyProvider)` and `Algorithm#ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey)` methods have been removed. +- `com.auth0.jwt.interfaces.Clock` has been removed. Instead, an implementation of `java.time.Clock` can be passed to the `BaseVerification` for testing purposes. +- `com.auth0.jwt.impl.NullClaim` has been removed. `Claim#isNull` can be used to determine if a claim's value is `null`. +- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.StandardClaims` and `com.auth0.jwt.HeaderParams`. + +### Behavioral potentially breaking changes + +#### JWT creation + +- All date/time claim values are now serialized as **seconds since the epoch**, in both the payload and header. In version 3, date/time claims nested in a list or map, as well as any header parameters with date/time values, were serialized as milliseconds since the epoch. +- When creating a JWT, passing `null` as the value no longer removes the claim if it was previously added to the builder. It now adds the claim with a `null` value. + +#### JWT validation + +- In version 3, specifying multiple claim expectations for the same claim name would override any previous expectations for that claim. In version 4, all expectations for that claim will be validated. +- In version 3, passing `null` for the value of a claim expectation would remove that expectation from the validation. In version 4, passing `null` does not remove that expectation, but instead validates that the claim has the literal value `null`. +- When validating a JWT, if an expected claim is present in the JWT but contains a value different from the one expected, an `IncorrectClaimException` (subclass of `InvalidClaimException`) will now be thrown instead of an `InvalidClaimException`. +- When validating a JWT, if an expected claim is not present in the JWT, an `MissingClaimException` (subclass of `InvalidClaimException`) will now be thrown instead of an `InvalidClaimException`. +- `withClaimPresence(String claimName)` now validates that the claim is present in the JWT, and a claim with a `null` value is considered present. Previously, a claim with a value of `null` would be considered as missing and fail the validation. +- When validating a date/time claim value, the validation no longer checks for strict equality of the claim's value and the provided `Date` (or `Instant`). Instead, the expected `Date` or `Instant` will be compared to the claim's value only considering seconds (because JWT date/time claims are represented as seconds since the epoch). + +#### Claim changes + +- `com.auth0.jwt.interfaces.Claim#isNull()` now returns true only if the claim is present and its value is `null`. Previously, it returned true if the claim was present and its value was `null`, or if the claim was not present in the JWT. To check if the claim is present or not in the JWT, use `isMissing()`. + +### New classes or methods + +#### `IncorrectClaimException` added + +This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim exists in the JWT but does not match the expected value. + +#### `MissingClaimException` added + +This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim is missing from the JWT. + +### `HeaderParams` added + +This class contains constants representing common header parameter names. + +### `StandardClaims` added + +This class contains constants representing the standard claim names. + +#### `JWTCreator` new methods + +- `JWTCreator.Builder#withExpiresAt(Instant expiresAt)` - adds the `exp` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withNotBefore(Instant notBefore)` - adds the `nbf` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withIssuedAt(Instant issuedAt)` - adds the `iat` claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withClaim(String claimName, Instant value)` - adds a claim to the JWT from a `java.time.Instant`. +- `JWTCreator.Builder#withNullClaim(String claimName)` - adds a claim to the JWT with the literal value `null`. + +#### `DecodedJWT` new methods + +- `Instant getExpiresAtAsInstant()` - Returns a JWT's `exp` claim as a `java.time.Instant`. +- `Instant getNotBeforeAsInstant()` - Returns a JWT's `nbf` claim as a `java.time.Instant`. +- `Instant getIssuedAtAsInstant()` - Returns a JWT's `iat` claim as a `java.time.Instant`. + +#### `Claim` new methods + +- `Instant asInstant()` - Gets a claim as a `java.time.Instant`. +- `boolean isMissing()` - Returns whether the claim is present or not. + +#### `Verification` new methods + +- `Verification withClaim(String name, Instant value)` - Adds an expectation that a claim with the provided name has a value equal to the provided `java.time.Instant`. +- `Verification withClaim(String name, BiPredicate predicate)` - Allows for a claim to be validated with the supplied predicate. +- `Verification withNullClaim(String name)` - Adds an expectation that a claim with the provided name has a value equal to the literal `null`. From e6565cecb1049c43f5dee0df5a0d545cc369c74e Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 20 Apr 2022 19:05:43 +0530 Subject: [PATCH 252/355] JavaDoc updated --- .../main/java/com/auth0/jwt/HeaderParams.java | 4 +- lib/src/main/java/com/auth0/jwt/JWT.java | 8 +- .../main/java/com/auth0/jwt/JWTCreator.java | 2 + .../main/java/com/auth0/jwt/JWTVerifier.java | 4 +- .../java/com/auth0/jwt/RegisteredClaims.java | 9 +- .../com/auth0/jwt/impl/PayloadSerializer.java | 2 - .../java/com/auth0/jwt/interfaces/Claim.java | 20 ++-- .../java/com/auth0/jwt/interfaces/Header.java | 4 +- .../auth0/jwt/interfaces/JWTPartsParser.java | 6 +- .../com/auth0/jwt/interfaces/JWTVerifier.java | 4 +- .../com/auth0/jwt/interfaces/Payload.java | 6 +- .../auth0/jwt/interfaces/Verification.java | 100 ++++++++---------- .../jwt/interfaces/VerificationTest.java | 5 + 13 files changed, 89 insertions(+), 85 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java index e15877a2..7af3c442 100644 --- a/lib/src/main/java/com/auth0/jwt/HeaderParams.java +++ b/lib/src/main/java/com/auth0/jwt/HeaderParams.java @@ -13,12 +13,12 @@ private HeaderParams() {} public static String ALGORITHM = "alg"; /** - * The content type of a JWT. + * The content type of the JWT. */ public static String CONTENT_TYPE = "cty"; /** - * The media type of a JWT. + * The media type of the JWT. */ public static String TYPE = "typ"; diff --git a/lib/src/main/java/com/auth0/jwt/JWT.java b/lib/src/main/java/com/auth0/jwt/JWT.java index 9d719d1e..696abe40 100644 --- a/lib/src/main/java/com/auth0/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/jwt/JWT.java @@ -26,7 +26,7 @@ public JWT() { * Decode a given Json Web Token. *

* Note that this method doesn't verify the token's signature! - * Use it only if you trust the token or you already verified it. + * Use it only if you trust the token or if you have already verified it. * * @param token with jwt format as string. * @return a decoded JWT. @@ -41,7 +41,7 @@ public DecodedJWT decodeJwt(String token) throws JWTDecodeException { * Decode a given Json Web Token. *

* Note that this method doesn't verify the token's signature! - * Use it only if you trust the token or you already verified it. + * Use it only if you trust the token or if you have already verified it. * * @param token with jwt format as string. * @return a decoded JWT. @@ -53,10 +53,10 @@ public static DecodedJWT decode(String token) throws JWTDecodeException { } /** - * Returns a {@link JWTVerifier} builder with the algorithm to be used to validate token signature. + * Returns a {@link Verification} builder with the algorithm to be used to validate token signature. * * @param algorithm that will be used to verify the token's signature. - * @return {@link JWTVerifier} builder + * @return {@link Verification} builder * @throws IllegalArgumentException if the provided algorithm is null. */ public static Verification require(Algorithm algorithm) { diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index cab474f7..25624982 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -19,6 +19,8 @@ * from a given Header and Payload content. *

* This class is thread-safe. + * + * TODO Poovam - Should we claim thread safety since KeyProvider implementation can cause signing differ */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 8b4639ac..0bc17fb1 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -35,10 +35,10 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { } /** - * Initialize a JWTVerifier instance using the given Algorithm. + * Initialize a {@link Verification} instance using the given Algorithm. * * @param algorithm the Algorithm to use on the JWT verification. - * @return a JWTVerifier.Verification instance to configure. + * @return a {@link Verification} instance to configure. * @throws IllegalArgumentException if the provided algorithm is null. */ static Verification init(Algorithm algorithm) throws IllegalArgumentException { diff --git a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index d60d9fc5..8c47eb51 100644 --- a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -1,7 +1,7 @@ package com.auth0.jwt; /** - * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1.1 of + * Contains constants representing the name of the Registered Claim Names as defined in Section 4.1 of * RFC 7529 */ public final class RegisteredClaims { @@ -11,37 +11,44 @@ private RegisteredClaims() { /** * The "iss" (issuer) claim identifies the principal that issued the JWT. + * Refer RFC 7529 Section 4.1.1 */ public static String ISSUER = "iss"; /** * The "sub" (subject) claim identifies the principal that is the subject of the JWT. + * Refer RFC 7529 Section 4.1.2 */ public static String SUBJECT = "sub"; /** * The "aud" (audience) claim identifies the recipients that the JWT is intended for. + * Refer RFC 7529 Section 4.1.3 */ public static String AUDIENCE = "aud"; /** * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be * accepted for processing. + * Refer RFC 7529 Section 4.1.4 */ public static String EXPIRES_AT = "exp"; /** * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. + * Refer RFC 7529 Section 4.1.5 */ public static String NOT_BEFORE = "nbf"; /** * The "iat" (issued at) claim identifies the time at which the JWT was issued. + * Refer RFC 7529 Section 4.1.6 */ public static String ISSUED_AT = "iat"; /** * The "jti" (JWT ID) claim provides a unique identifier for the JWT. + * Refer RFC 7529 Section 4.1.7 */ public static String JWT_ID = "jti"; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index c8df2bb9..24fe37b7 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -34,8 +34,6 @@ protected void writeClaim(Map.Entry entry, JsonGenerator gen) th * Audience may be a list of strings or a single string. This is needed to properly handle the aud claim when * added with the {@linkplain com.auth0.jwt.JWTCreator.Builder#withPayload(Map)} method. */ - - // private void writeAudience(JsonGenerator gen, Map.Entry e) throws IOException { if (e.getValue() instanceof String) { gen.writeFieldName(e.getKey()); diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index f6ccfdcd..7c4da4f7 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -22,7 +22,7 @@ public interface Claim { /** * Can be used to verify whether the Claim is found or not. - * This will be true even if the Claim has null value associated to it. + * This will be true even if the Claim has {@code null} value associated to it. * * @return whether this Claim is present or not */ @@ -30,7 +30,7 @@ public interface Claim { /** * Get this Claim as a Boolean. - * If the value isn't of type Boolean or it can't be converted to a Boolean, null will be returned. + * If the value isn't of type Boolean or it can't be converted to a Boolean, {@code null} will be returned. * * @return the value as a Boolean or null. */ @@ -38,7 +38,7 @@ public interface Claim { /** * Get this Claim as an Integer. - * If the value isn't of type Integer or it can't be converted to an Integer, null will be returned. + * If the value isn't of type Integer or it can't be converted to an Integer, {@code null} will be returned. * * @return the value as an Integer or null. */ @@ -46,7 +46,7 @@ public interface Claim { /** * Get this Claim as an Long. - * If the value isn't of type Long or it can't be converted to an Long, null will be returned. + * If the value isn't of type Long or it can't be converted to a Long, {@code null} will be returned. * * @return the value as an Long or null. */ @@ -54,7 +54,7 @@ public interface Claim { /** * Get this Claim as a Double. - * If the value isn't of type Double or it can't be converted to a Double, null will be returned. + * If the value isn't of type Double or it can't be converted to a Double, {@code null} will be returned. * * @return the value as a Double or null. */ @@ -62,7 +62,7 @@ public interface Claim { /** * Get this Claim as a String. - * If the value isn't of type String or it can't be converted to a String, null will be returned. + * If the value isn't of type String or it can't be converted to a String, {@code null} will be returned. * * @return the value as a String or null. */ @@ -70,7 +70,7 @@ public interface Claim { /** * Get this Claim as a Date. - * If the value can't be converted to a Date, null will be returned. + * If the value can't be converted to a Date, {@code null} will be returned. * * @return the value as a Date or null. */ @@ -78,7 +78,7 @@ public interface Claim { /** * Get this Claim as an Instant. - * If the value can't be converted to an Instant, null will be returned. + * If the value can't be converted to an Instant, {@code null} will be returned. * * @return the value as a Date or null. */ @@ -89,7 +89,7 @@ default Instant asInstant() { /** * Get this Claim as an Array of type T. - * If the value isn't an Array, null will be returned. + * If the value isn't an Array, {@code null} will be returned. * * @param type * @param clazz the type class @@ -100,7 +100,7 @@ default Instant asInstant() { /** * Get this Claim as a List of type T. - * If the value isn't an Array, null will be returned. + * If the value isn't an Array, {@code null} will be returned. * * @param type * @param clazz the type class diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java index 030643d0..52b3ba56 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Header.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Header.java @@ -1,7 +1,7 @@ package com.auth0.jwt.interfaces; /** - * The Header class represents the 1st part of the JWT, where the Header value is hold. + * The Header class represents the 1st part of the JWT, where the Header value is held. */ public interface Header { @@ -35,7 +35,7 @@ public interface Header { /** * Get a Private Claim given it's name. If the Claim wasn't specified in the Header, a 'null claim' will be - * returned. All of the methods of that claim will return {@code null}. + * returned. All the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java index e1c6efcc..33cd0d70 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTPartsParser.java @@ -4,12 +4,12 @@ /** * The JWTPartsParser class defines which parts of the JWT should be converted - * to it's specific Object representation instance. + * to its specific Object representation instance. */ public interface JWTPartsParser { /** - * Parses the given JSON into a Payload instance. + * Parses the given JSON into a {@link Payload} instance. * * @param json the content of the Payload in a JSON representation. * @return the Payload. @@ -18,7 +18,7 @@ public interface JWTPartsParser { Payload parsePayload(String json) throws JWTDecodeException; /** - * Parses the given JSON into a Header instance. + * Parses the given JSON into a {@link Header} instance. * * @param json the content of the Header in a JSON representation. * @return the Header. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index 3555878e..b7030a97 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -4,7 +4,7 @@ /** - * Used to verify the JWT for it's signature and claims. + * Used to verify the JWT for its signature and claims. */ public interface JWTVerifier { @@ -18,7 +18,7 @@ public interface JWTVerifier { DecodedJWT verify(String token) throws JWTVerificationException; /** - * Performs the verification against the given decoded JWT. + * Performs the verification against the given {@link DecodedJWT}. * * @param jwt to verify. * @return a verified and decoded JWT. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java index b3bf71f5..feb58c64 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Payload.java @@ -6,7 +6,7 @@ import java.util.Map; /** - * The Payload class represents the 2nd part of the JWT, where the Payload value is hold. + * The Payload class represents the 2nd part of the JWT, where the Payload value is held. */ public interface Payload { @@ -87,8 +87,8 @@ default Instant getIssuedAtAsInstant() { String getId(); /** - * Get a Claim given it's name. If the Claim wasn't specified in the Payload, a 'null claim' - * will be returned. All of the methods of that claim will return {@code null}. + * Get a Claim given its name. If the Claim wasn't specified in the Payload, a 'null claim' + * will be returned. All the methods of that claim will return {@code null}. * * @param name the name of the Claim to retrieve. * @return a non-null Claim. diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 8b0416dc..4a8a0f84 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -7,12 +7,13 @@ import java.util.function.BiPredicate; /** - * Holds the Claims and claim-based configurations required for a JWT to be considered valid. + * Constructs and holds the checks required for a JWT to be considered valid. */ public interface Verification { /** - * Require a specific Issuer ("iss") claim. + * Verifies whether the JWT contains an Issuer ("iss") claim that equals to the value provided. + * This check is case-sensitive. * * @param issuer the required Issuer value. * @return this same Verification instance. @@ -22,7 +23,8 @@ default Verification withIssuer(String issuer) { } /** - * Require a specific Issuer ("iss") claim. + * Verifies whether the JWT contains an Issuer ("iss") claim that contains all the values provided. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param issuer the required Issuer value. If multiple values are given, the claim must at least match one of them * @return this same Verification instance. @@ -30,7 +32,8 @@ default Verification withIssuer(String issuer) { Verification withIssuer(String... issuer); /** - * Require a specific Subject ("sub") claim. + * Verifies whether the JWT contains a Subject ("sub") claim that equals to the value provided. + * This check is case-sensitive. * * @param subject the required Subject value * @return this same Verification instance. @@ -38,11 +41,8 @@ default Verification withIssuer(String issuer) { Verification withSubject(String subject); /** - * Require a specific Audience ("aud") claim. If multiple audiences are specified, they must all be present - * in the "aud" claim. - * - * If this is used in conjunction with {@link #withAnyOfAudience(String...)}, whichever one is configured last will - * determine the audience validation behavior. + * Verifies whether the JWT contains an Audience ("aud") claim that contains all the values provided. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param audience the required Audience value * @return this same Verification instance. @@ -50,24 +50,13 @@ default Verification withIssuer(String issuer) { Verification withAudience(String... audience); /** - * Require that the Audience ("aud") claim contain at least one of the specified audiences. - * - * If this is used in conjunction with {@link #withAudience(String...)}, whichever one is configured last will - * determine the audience validation behavior. - * - * Note: This method was added after the interface was released. - * It is defined as a default method for compatibility reasons. - * From version 4.0 on, the method will be abstract and all implementations of this interface - * will have to provide their own implementation. - * - * The default implementation throws an {@linkplain UnsupportedOperationException}. + * Verifies whether the JWT contains an Audience ("aud") claim contain at least one of the specified audiences. + * This check is case-sensitive. An empty array is considered as a {@code null}. * * @param audience the required Audience value for which the "aud" claim must contain at least one value. * @return this same Verification instance. */ - default Verification withAnyOfAudience(String... audience) { - throw new UnsupportedOperationException("withAnyOfAudience"); - } + Verification withAnyOfAudience(String... audience); /** * Define the default window in seconds in which the Not Before, Issued At and Expires At Claims @@ -116,149 +105,152 @@ default Verification withAnyOfAudience(String... audience) { Verification acceptIssuedAt(long leeway) throws IllegalArgumentException; /** - * Require a specific JWT Id ("jti") claim. + * Verifies whether the JWT contains a JWT ID ("jti") claim that equals to the value provided. + * This check is case-sensitive. * - * @param jwtId the required Id value + * @param jwtId the required ID value * @return this same Verification instance. */ Verification withJWTId(String jwtId); /** - * Require a claim to be present, with any value. + * Verifies whether the claim is present in the JWT, with any value including {@code null}. * * @param name the Claim's name. * @return this same Verification instance - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaimPresence(String name) throws IllegalArgumentException; /** - * Require a specific Claim value to be null. + * Verifies whether the claim is present with a {@code null} value. * * @param name the Claim's name. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withNullClaim(String name) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Boolean value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Boolean value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Integer value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Integer value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Long value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Long value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given Integer value. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Double value) throws IllegalArgumentException; /** - * Require a specific Claim value. + * Verifies whether the claim is equal to the given String value. + * This check is case-sensitive. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, String value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * Verifies whether the claim is equal to the given Date value. + * Note that date-time claims are serialized as seconds since the epoch; * when verifying date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, Date value) throws IllegalArgumentException; /** - * Require a specific Claim value. Note that date-time claims are serialized as seconds since the epoch; + * Verifies whether the claim is equal to the given Instant value. + * Note that date-time claims are serialized as seconds since the epoch; * when verifying a date-time claim value, any time units more granular than seconds will not be considered. * * @param name the Claim's name. * @param value the Claim's value. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ default Verification withClaim(String name, Instant value) throws IllegalArgumentException { return withClaim(name, value != null ? Date.from(value) : null); } /** - * Executes the predicate provided during the verification - * and passes the verification if the predicate returns true. + * Executes the predicate provided and the validates the JWT if the predicate returns true. * * @param name the Claim's name * @param predicate the predicate check to be done. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withClaim(String name, BiPredicate predicate) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given String items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, String... items) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given Integer items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException; /** - * Require a specific Array Claim to contain at least the given items. + * Verifies whether the claim contain at least the given Long items. * * @param name the Claim's name. * @param items the items the Claim must contain. * @return this same Verification instance. - * @throws IllegalArgumentException if the name is null. + * @throws IllegalArgumentException if the name is {@code null}. */ Verification withArrayClaim(String name, Long ... items) throws IllegalArgumentException; /** - * Skip the Issued At ("iat") date verification. By default, the verification is performed. + * Skip the Issued At ("iat") claim verification. By default, the verification is performed. * * @return this same Verification instance. */ @@ -267,7 +259,7 @@ default Verification withClaim(String name, Instant value) throws IllegalArgumen /** * Creates a new and reusable instance of the JWTVerifier with the configuration already provided. * - * @return a new JWTVerifier instance. + * @return a new {@link com.auth0.jwt.interfaces.JWTVerifier} instance. */ JWTVerifier build(); } diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index fb0c74a9..bdbd688a 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -74,6 +74,11 @@ public Verification withAudience(String... audience) { return null; } + @Override + public Verification withAnyOfAudience(String... audience) { + return null; + } + @Override public Verification acceptLeeway(long leeway) throws IllegalArgumentException { return null; From 1979b95f272cc4bffda318a7db097bbed66a5d82 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 20 Apr 2022 19:45:09 +0530 Subject: [PATCH 253/355] Removed test for default audience claim check --- .../java/com/auth0/jwt/interfaces/VerificationTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java index bdbd688a..11acb029 100644 --- a/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java +++ b/lib/src/test/java/com/auth0/jwt/interfaces/VerificationTest.java @@ -38,13 +38,6 @@ public void withInstantClaimShouldUseDefaultImplAndHandleNull() { assertThat(((VerificationImplForTest)verification).expectedClaims, hasEntry("name", null)); } - @Test - public void withAnyOfAudienceDeafultImplShouldThrow() { - assertThrows("withAnyOfAudience", UnsupportedOperationException.class, () -> { - new VerificationImplForTest().withAnyOfAudience(""); - }); - } - @Test public void withIssuerStringDefaultImplShouldDelegate() { Verification verification = new VerificationImplForTest() From fef52cc0bd7b1039436ebe09abbe569123812b02 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 20 Apr 2022 17:27:14 -0500 Subject: [PATCH 254/355] Formatting fixes --- MIGRATION_GUIDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1f95f323..7105742d 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -50,11 +50,11 @@ This class extends `InvalidClaimException` and represents that when validating a This class extends `InvalidClaimException` and represents that when validating a JWT, an expected claim is missing from the JWT. -### `HeaderParams` added +#### `HeaderParams` added This class contains constants representing common header parameter names. -### `StandardClaims` added +#### `StandardClaims` added This class contains constants representing the standard claim names. From 3f939842f1d0c6c393b9fd6d2365fa2e45d986cc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 21 Apr 2022 18:49:01 +0530 Subject: [PATCH 255/355] Added note on Keyprovider --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 -- lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 25624982..cab474f7 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -19,8 +19,6 @@ * from a given Header and Payload content. *

* This class is thread-safe. - * - * TODO Poovam - Should we claim thread safety since KeyProvider implementation can cause signing differ */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java index fd8baea5..30a144a6 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java @@ -5,6 +5,7 @@ /** * Generic Public/Private Key provider. + * While implementing, ensure the Private Key and Private Key ID doesn't change in between signing a token. * * @param the class that represents the Public Key * @param the class that represents the Private Key From 0d3864da70b1831d4a923450d41cd784ba181c91 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Thu, 21 Apr 2022 10:31:43 -0500 Subject: [PATCH 256/355] updates from PR review --- MIGRATION_GUIDE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 7105742d..c47b95c2 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -18,7 +18,8 @@ This guide captures the changes you should be aware of when planning and upgradi - Support for the ES256K algorithm has been removed, as it is disabled in Java 15+. The `Algorithm#ECDSA256K(ECDSAKeyProvider keyProvider)` and `Algorithm#ECDSA256K(ECPublicKey publicKey, ECPrivateKey privateKey)` methods have been removed. - `com.auth0.jwt.interfaces.Clock` has been removed. Instead, an implementation of `java.time.Clock` can be passed to the `BaseVerification` for testing purposes. - `com.auth0.jwt.impl.NullClaim` has been removed. `Claim#isNull` can be used to determine if a claim's value is `null`. -- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.StandardClaims` and `com.auth0.jwt.HeaderParams`. +- `com.auth0.jwt.impl.PublicClaims` was removed, and replaced by `com.auth0.jwt.RegisteredClaims` and `com.auth0.jwt.HeaderParams`. +- `com.auth0.jwt.interfaces.Verification#withAnyOfAudience` no longer provides a default implementation. ### Behavioral potentially breaking changes @@ -54,9 +55,9 @@ This class extends `InvalidClaimException` and represents that when validating a This class contains constants representing common header parameter names. -#### `StandardClaims` added +#### `RegisteredClaims` added -This class contains constants representing the standard claim names. +This class contains constants representing the registered claim names. #### `JWTCreator` new methods From b6a9a034b3ceac2e644308e376c0f48aefdcc6da Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 25 Apr 2022 19:31:01 -0500 Subject: [PATCH 257/355] Documentation updates --- .codecov.yml | 1 + README.md | 39 ++++++++++++++++--- .../main/java/com/auth0/jwt/JWTCreator.java | 2 +- .../java/com/auth0/jwt/JWTCreatorTest.java | 6 +-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 51f857af..de278af2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,5 +11,6 @@ coverage: project: default: target: auto + threshold: 1% if_no_uploads: error comment: false \ No newline at end of file diff --git a/README.md b/README.md index 065899f1..624a7f68 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.or If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. -> This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. +> You are viewing the documentation for the v4 beta release. For the latest stable release, please see the [version 3.x documentation](https://github.com/auth0/java-jwt). ## Table of Contents +- [**Requirements**](#requirements) - [**Installation**](#installation) - [**Available Algorithms**](#available-algorithms) - [**Quickstart**](#quickstart) + [**Create and Sign a Token**](#create-and-sign-a-token) + [**Verify a Token**](#verify-a-token) - + [**Decode a Token**](#decode-a-token) - [**Usage**](#usage) + [**Pick the algorithm**](#pick-the-algorithm) + [**Time Validation**](#time-validation) @@ -28,6 +28,10 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o + [**Claim Class**](#claim-class) - [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) +## Requirements + +This library is supported for Java 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. + ## Installation ### Gradle @@ -138,8 +142,13 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. +

+Need to peek into a JWT without verifying it? (Click to expand) + ### Decode a Token +> __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `JWTVerifier` as documented above instead. + ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { @@ -151,6 +160,8 @@ try { If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. +
+ ## Usage ### Pick the Algorithm @@ -176,7 +187,7 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); ##### HMAC Key Length and Security -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recomendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. +When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: @@ -243,13 +254,13 @@ JWTVerifier verifier = JWT.require(algorithm) .build(); ``` -If you need to test this behaviour in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a custom `Clock`. e.g.: +If you need to test this behavior in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: ```java BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); -Clock clock = new CustomClock(); //Must implement Clock interface +private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); JWTVerifier verifier = verification.build(clock); ``` @@ -342,6 +353,12 @@ Returns the Expiration Time value or null if it's not defined in the Payload. Date expiresAt = jwt.getExpiresAt(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant expiresAt = jwt.getExpiresAtAsInstant(); +``` + #### Not Before ("nbf") Returns the Not Before value or null if it's not defined in the Payload. @@ -350,6 +367,12 @@ Returns the Not Before value or null if it's not defined in the Payload. Date notBefore = jwt.getNotBefore(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant notBefore = jwt.getNotBeforeAsInstant(); +``` + #### Issued At ("iat") Returns the Issued At value or null if it's not defined in the Payload. @@ -358,6 +381,12 @@ Returns the Issued At value or null if it's not defined in the Payload. Date issuedAt = jwt.getIssuedAt(); ``` +If you prefer to work with `java.time.Instant` instead of `java.util.Date`: + +```java +Instant issuedAt = jwt.getIssuedAtAsInstant(); +``` + #### JWT ID ("jti") Returns the JWT ID value or null if it's not defined in the Payload. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index cab474f7..a99f0fa0 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -453,7 +453,7 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE if (!validatePayload(payloadClaims)) { throw new IllegalArgumentException("Claim values must only be of types Map, List, Boolean, Integer, " - + "Long, Double, String, Date and Null"); + + "Long, Double, String, Date, Instant, and Null"); } // add claims only after validating all claims so as not to corrupt the claims map of this builder diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index efe6b94b..4d1c7314 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -759,7 +759,7 @@ public void shouldOverwriteExistingPayloadWhenSettingSamePayloadKey() { @Test public void withPayloadShouldNotAllowCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); @@ -786,7 +786,7 @@ public void withPayloadShouldAllowNullListItems() { @Test public void withPayloadShouldNotAllowListWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("list", Arrays.asList("item1", new UserPojo("name", 42))); @@ -798,7 +798,7 @@ public void withPayloadShouldNotAllowListWithCustomType() { @Test public void withPayloadShouldNotAllowMapWithCustomType() { exception.expect(IllegalArgumentException.class); - exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date and Null"); + exception.expectMessage("Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String, Date, Instant, and Null"); Map payload = new HashMap<>(); payload.put("entry", "value"); From 05b54499be0cc06478467d66c801d9bc3da3fecc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 15:46:47 +0200 Subject: [PATCH 258/355] Added protection against CVE-2022-21449 --- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 69 ++++++++- .../jwt/algorithms/ECDSAAlgorithmTest.java | 139 +++++++++++++++++- .../ECDSABouncyCastleProviderTests.java | 20 ++- 3 files changed, 221 insertions(+), 7 deletions(-) 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 c2d08a15..f96fd576 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -46,6 +47,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } + validateSignatureStructure(signatureBytes, publicKey); boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { @@ -140,13 +142,42 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { return joseSignature; } - //Visible for testing - byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { + /** + * Added check for extra protection against CVE-2022-21449. + * This method ensures the signature's structure is as expected. + * + * @param joseSignature is the signature from the JWT + * @param publicKey public key used to verify the JWT + * @throws SignatureException if the signature's structure is not as per expectation + */ + // Visible for testing + void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) throws SignatureException { + // check signature length, moved this check from JOSEToDER method if (joseSignature.length != ecNumberSize * 2) { throw new SignatureException("Invalid JOSE signature format."); } - // Retrieve R and S number's length and padding. + if (isAllZeros(joseSignature)) { + throw new SignatureException("Invalid Signature: All Zeros."); + } + + // get R + byte[] rBytes = new byte[ecNumberSize]; + System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); + BigInteger r = new BigInteger(1, rBytes); + if(isAllZeros(rBytes)) { + throw new SignatureException("Invalid Signature: All Zeros for R value."); + } + + // get S + byte[] sBytes = new byte[ecNumberSize]; + System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); + BigInteger s = new BigInteger(1, sBytes); + if(isAllZeros(sBytes)) { + throw new SignatureException("Invalid Signature: All Zeros for S value."); + } + + //moved this check from JOSEToDER method int rPadding = countPadding(joseSignature, 0, ecNumberSize); int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); int rLength = ecNumberSize - rPadding; @@ -157,6 +188,29 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { throw new SignatureException("Invalid JOSE signature format."); } + // check for 0 r or s here since we have the values + if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) { + throw new SignatureException("R or S value cannot be zero."); + } + + BigInteger order = publicKey.getParams().getOrder(); + + // R and S must be less than N + if (order.compareTo(r) < 1 || order.compareTo(s) < 1) { + throw new SignatureException("The difference between R or S value and order should be greater than one."); + } + } + + //Visible for testing + byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { + // Retrieve R and S number's length and padding. + int rPadding = countPadding(joseSignature, 0, ecNumberSize); + int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); + int rLength = ecNumberSize - rPadding; + int sLength = ecNumberSize - sPadding; + + int length = 2 + rLength + 2 + sLength; + final byte[] derSignature; int offset; if (length > 0x7f) { @@ -205,6 +259,15 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { return derSignature; } + private boolean isAllZeros(byte[] bytes) { + for (byte b : bytes) { + if (b != 0) { + return false; + } + } + return true; + } + private int countPadding(byte[] bytes, int fromIndex, int toIndex) { int padding = 0; while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) { 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 39c9dabf..aee8238c 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -4,6 +4,7 @@ import com.auth0.jwt.exceptions.SignatureGenerationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import com.auth0.jwt.interfaces.JWTVerifier; import org.hamcrest.Matchers; import org.hamcrest.collection.IsIn; import org.junit.Assert; @@ -12,11 +13,13 @@ import org.junit.rules.ExpectedException; import java.io.ByteArrayOutputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; import java.util.Arrays; import java.util.Base64; @@ -574,6 +577,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -592,6 +599,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -610,6 +621,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -939,12 +954,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt @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")); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (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); + algorithm256.validateSignatureStructure(joseSignature, publicKey); } @Test @@ -1309,4 +1325,123 @@ public void shouldFailOnECDSA256SigningWithDeprecatedMethodWhenProvidedPrivateKe algorithm.sign(new byte[0]); } + @Test + public void invalidECDSA256SignatureShouldFailTokenVerification() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(SignatureException.class)); + + String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0._____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); + JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); + JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); + JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); + verifier256.verify(jwtWithInvalidSig); + verifier384.verify(jwtWithInvalidSig); + verifier512.verify(jwtWithInvalidSig); + verifier256k.verify(jwtWithInvalidSig); + } + + @Test + public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectCause(isA(SignatureException.class)); + + String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); + JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); + JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); + JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); + JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); + verifier256.verify(jwtWithInvalidSig); + verifier384.verify(jwtWithInvalidSig); + verifier512.verify(jwtWithInvalidSig); + verifier256k.verify(jwtWithInvalidSig); + } + + @Test + public void signatureWithAllZerosShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(pubKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] signatureBytes = new byte[64]; + algorithm256.validateSignatureStructure(signatureBytes, pubKey); + } + + @Test + public void signatureWithRZeroShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + + String[] chunks = signedJwt.split("\\."); + byte[] signature = Base64.getUrlDecoder().decode(chunks[2]); + + byte[] sigWithBlankR = new byte[signature.length]; + for (int i = 0; i < signature.length; i++) { + if (i < signature.length / 2) { + sigWithBlankR[i] = 0; + } else { + sigWithBlankR[i] = signature[i]; + } + } + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(sigWithBlankR, publicKey); + } + + @Test + public void signatureWithSZeroShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + + String[] chunks = signedJwt.split("\\."); + byte[] signature = Base64.getUrlDecoder().decode(chunks[2]); + + byte[] sigWithBlankS = new byte[signature.length]; + for (int i = 0; i < signature.length; i++) { + if (i < signature.length / 2) { + sigWithBlankS[i] = signature[i]; + } else { + sigWithBlankS[i] = 0; + } + } + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(sigWithBlankS, publicKey); + } + + @Test + public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + String[] chunks = jwtWithInvalidSig.split("\\."); + byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(invalidSignature, publicKey); + } } \ 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 index 68fe6e50..78e0063d 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -11,11 +11,14 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.util.Arrays; import java.util.Base64; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; @@ -591,6 +594,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce .thenThrow(NoSuchAlgorithmException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -609,6 +616,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { .thenThrow(InvalidKeyException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -627,6 +638,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception .thenThrow(SignatureException.class); ECPublicKey publicKey = mock(ECPublicKey.class); + when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class)); + byte[] a = new byte[64]; + Arrays.fill(a, Byte.MAX_VALUE); + when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a)); ECPrivateKey privateKey = mock(ECPrivateKey.class); ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); @@ -935,12 +950,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt @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")); + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (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); + algorithm256.validateSignatureStructure(joseSignature, publicKey); } @Test From 69dae3eba9235a45e01ab588d5027b73aef918cc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 16:16:36 +0200 Subject: [PATCH 259/355] Added banner to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 260f6d6e..0e6a7d48 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). +> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. + If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. > This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0. From 3135ba7893ea60882500fa509c6d98fe48a4f0f8 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 16:18:01 +0200 Subject: [PATCH 260/355] Removed zero check since it is already verified --- .../main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 5 ----- 1 file changed, 5 deletions(-) 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 f96fd576..1d2544f6 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -188,11 +188,6 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr throw new SignatureException("Invalid JOSE signature format."); } - // check for 0 r or s here since we have the values - if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) { - throw new SignatureException("R or S value cannot be zero."); - } - BigInteger order = publicKey.getParams().getOrder(); // R and S must be less than N From 208d7b66c281fcd91dbc5d83fe51d676ee5e0d51 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 3 May 2022 18:37:52 +0200 Subject: [PATCH 261/355] Improved order comparison --- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 8 ++++++-- .../jwt/algorithms/ECDSAAlgorithmTest.java | 20 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) 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 1d2544f6..00b68cad 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -191,8 +191,12 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr BigInteger order = publicKey.getParams().getOrder(); // R and S must be less than N - if (order.compareTo(r) < 1 || order.compareTo(s) < 1) { - throw new SignatureException("The difference between R or S value and order should be greater than one."); + if (order.compareTo(r) < 1) { + throw new SignatureException("The difference between R value and order should be greater than one."); + } + + if (order.compareTo(s) < 1){ + throw new SignatureException("The difference between S value and order should be greater than one."); } } 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 aee8238c..a02c639b 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1429,7 +1429,7 @@ public void signatureWithSZeroShouldFail() throws Exception { } @Test - public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { + public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1444,4 +1444,22 @@ public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception { ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); algorithm256.validateSignatureStructure(invalidSignature, publicKey); } + + @Test + public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { + exception.expect(SignatureException.class); + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + + String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey)); + String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"; + + String[] chunks = jwtWithInvalidSig.split("\\."); + byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]); + invalidSignature[0] = Byte.MAX_VALUE; + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey); + algorithm256.validateSignatureStructure(invalidSignature, publicKey); + } } \ No newline at end of file From df004b1bc55ed42f9ccb1d2b820bad08879ad273 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 4 May 2022 11:38:43 +0200 Subject: [PATCH 262/355] Updated documentation regarding HMAC Key length --- README.md | 8 ++++---- .../com/auth0/jwt/algorithms/Algorithm.java | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 624a7f68..3e270500 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` ```java String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { - Algorithm algorithm = Algorithm.HMAC256("secret"); + Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance @@ -175,7 +175,7 @@ When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can av ```java //HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); +Algorithm algorithmHS = Algorithm.HMAC256("secret"); //use more secure key //RSA RSAPublicKey publicKey = //Get the key instance @@ -185,9 +185,9 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). -##### HMAC Key Length and Security +##### :key: HMAC Key Length and Security -When using a Hash-based Message Authenticaton Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. +When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. #### Using a KeyProvider: By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index a5247005..27d79909 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -130,7 +130,9 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 256 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -142,6 +144,8 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 256 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -152,7 +156,9 @@ public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 384 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -164,6 +170,8 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 384 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -174,7 +182,9 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { /** * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * - * @param secret the secret to use in the verify or signing instance. + * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 512 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -186,6 +196,8 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { * Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512". * * @param secret the secret bytes to use in the verify or signing instance. + * Ensure the length of the secret is at least 512 bit long + * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ From 645b40a4c9ee9ed62e49d5bebda2c8a6c9d3221a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 11:54:49 +0200 Subject: [PATCH 263/355] Added test cases to differentiate thrown exception --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 5 +++++ 1 file changed, 5 insertions(+) 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 a02c639b..44d9e6db 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1370,6 +1370,7 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception @Test public void signatureWithAllZerosShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros."); ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1381,6 +1382,7 @@ public void signatureWithAllZerosShouldFail() throws Exception { @Test public void signatureWithRZeroShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros for R value."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1406,6 +1408,7 @@ public void signatureWithRZeroShouldFail() throws Exception { @Test public void signatureWithSZeroShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("Invalid Signature: All Zeros for S value."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1431,6 +1434,7 @@ public void signatureWithSZeroShouldFail() throws Exception { @Test public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("The difference between R value and order should be greater than one."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1448,6 +1452,7 @@ public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { @Test public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); + exception.expectMessage("The difference between S value and order should be greater than one."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); From 9d471d2152354866bfa65a861dac5590601efe79 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 14:10:52 +0200 Subject: [PATCH 264/355] Abstracted the error message thrown --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 10 +++++----- .../com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) 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 00b68cad..ac34c44e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -158,7 +158,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr } if (isAllZeros(joseSignature)) { - throw new SignatureException("Invalid Signature: All Zeros."); + throw new SignatureException("Invalid signature format."); } // get R @@ -166,7 +166,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); BigInteger r = new BigInteger(1, rBytes); if(isAllZeros(rBytes)) { - throw new SignatureException("Invalid Signature: All Zeros for R value."); + throw new SignatureException("Invalid signature format."); } // get S @@ -174,7 +174,7 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); BigInteger s = new BigInteger(1, sBytes); if(isAllZeros(sBytes)) { - throw new SignatureException("Invalid Signature: All Zeros for S value."); + throw new SignatureException("Invalid signature format."); } //moved this check from JOSEToDER method @@ -192,11 +192,11 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr // R and S must be less than N if (order.compareTo(r) < 1) { - throw new SignatureException("The difference between R value and order should be greater than one."); + throw new SignatureException("Invalid signature format."); } if (order.compareTo(s) < 1){ - throw new SignatureException("The difference between S value and order should be greater than one."); + throw new SignatureException("Invalid signature format."); } } 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 44d9e6db..818924fe 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1370,7 +1370,7 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception @Test public void signatureWithAllZerosShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros."); + exception.expectMessage("Invalid signature format."); ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); @@ -1382,7 +1382,7 @@ public void signatureWithAllZerosShouldFail() throws Exception { @Test public void signatureWithRZeroShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros for R value."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1408,7 +1408,7 @@ public void signatureWithRZeroShouldFail() throws Exception { @Test public void signatureWithSZeroShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("Invalid Signature: All Zeros for S value."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1434,7 +1434,7 @@ public void signatureWithSZeroShouldFail() throws Exception { @Test public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("The difference between R value and order should be greater than one."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); @@ -1452,7 +1452,7 @@ public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception { @Test public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception { exception.expect(SignatureException.class); - exception.expectMessage("The difference between S value and order should be greater than one."); + exception.expectMessage("Invalid signature format."); ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); From fd3eac3b721c7481a69d9109cc0666b65544ac5b Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Thu, 5 May 2022 17:17:28 +0200 Subject: [PATCH 265/355] Release 3.19.2 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a022328d..d81f9a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) + +**Security** +- [SDK-3311] Added protection against CVE-2022-21449 [\#579](https://github.com/auth0/java-jwt/pull/579) ([poovamraj](https://github.com/poovamraj)) + ## [3.19.1](https://github.com/auth0/java-jwt/tree/3.19.1) (2022-03-30) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.0...3.19.1) diff --git a/README.md b/README.md index 0e6a7d48..c55fa70c 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ The library is available on both Maven Central and Bintray, and the Javadoc is p com.auth0 java-jwt - 3.19.1 + 3.19.2 ``` ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.1' +implementation 'com.auth0:java-jwt:3.19.2' ``` ## Available Algorithms From 4573ba0537c5a34b768be4149ab817075cdbd111 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 13:02:42 +0200 Subject: [PATCH 266/355] Code formatting for CheckStyle --- config/checkstyle/checkstyle.xml | 2 +- .../com/auth0/jwt/algorithms/ECDSAAlgorithm.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d2615981..b97ccc37 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -206,7 +206,7 @@ value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> - + 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 185d95cf..b3046097 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -50,7 +50,8 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { throw new IllegalStateException("The given Public Key is null."); } validateSignatureStructure(signatureBytes, publicKey); - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); + boolean valid = crypto.verifySignatureFor( + getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); @@ -151,7 +152,7 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException { * This method ensures the signature's structure is as expected. * * @param joseSignature is the signature from the JWT - * @param publicKey public key used to verify the JWT + * @param publicKey public key used to verify the JWT * @throws SignatureException if the signature's structure is not as per expectation */ // Visible for testing @@ -168,16 +169,14 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr // get R byte[] rBytes = new byte[ecNumberSize]; System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize); - BigInteger r = new BigInteger(1, rBytes); - if(isAllZeros(rBytes)) { + if (isAllZeros(rBytes)) { throw new SignatureException("Invalid signature format."); } // get S byte[] sBytes = new byte[ecNumberSize]; System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize); - BigInteger s = new BigInteger(1, sBytes); - if(isAllZeros(sBytes)) { + if (isAllZeros(sBytes)) { throw new SignatureException("Invalid signature format."); } @@ -193,13 +192,15 @@ void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) thr } BigInteger order = publicKey.getParams().getOrder(); + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); // R and S must be less than N if (order.compareTo(r) < 1) { throw new SignatureException("Invalid signature format."); } - if (order.compareTo(s) < 1){ + if (order.compareTo(s) < 1) { throw new SignatureException("Invalid signature format."); } } From 6f94d823682cf4ddb717d9e336c6f49004078b4b Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 13:15:37 +0200 Subject: [PATCH 267/355] Removed ES256K tests since it is removed in v4 --- .../java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java | 6 ------ 1 file changed, 6 deletions(-) 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 d05fb53d..2e636c71 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -1223,15 +1223,12 @@ public void invalidECDSA256SignatureShouldFailTokenVerification() throws Excepti ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); - JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); verifier256.verify(jwtWithInvalidSig); verifier384.verify(jwtWithInvalidSig); verifier512.verify(jwtWithInvalidSig); - verifier256k.verify(jwtWithInvalidSig); } @Test @@ -1244,15 +1241,12 @@ public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC"); JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build(); JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build(); JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build(); - JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build(); verifier256.verify(jwtWithInvalidSig); verifier384.verify(jwtWithInvalidSig); verifier512.verify(jwtWithInvalidSig); - verifier256k.verify(jwtWithInvalidSig); } @Test From c2e3558feb5cf6bd12f977b692236005053962fd Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 14:46:45 +0200 Subject: [PATCH 268/355] Added tests to verify null creation in list and map --- .../java/com/auth0/jwt/JWTCreatorTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 4d1c7314..020e5e37 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -16,6 +16,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -919,4 +922,22 @@ public void withPayloadShouldSupportNullValuesEverywhere() { assertThat(headerJson, JsonMatcher.hasEntry("listNestedClaim", listNestedClaim)); assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); } + + @Test + public void shouldCreatePayloadWithNullForMap() { + String jwt = JWTCreator.init() + .withClaim("name", (Map) null) + .sign(Algorithm.HMAC256("secret")); + assertThat(jwt, is(notNullValue())); + assertTrue(JWT.decode(jwt).getClaim("name").isNull()); + } + + @Test + public void shouldCreatePayloadWithNullForList() { + String jwt = JWTCreator.init() + .withClaim("name", (List) null) + .sign(Algorithm.HMAC256("secret")); + assertThat(jwt, is(notNullValue())); + assertTrue(JWT.decode(jwt).getClaim("name").isNull()); + } } From 08f2e1de7ad86ad85a182c2d8a01ec04358be5ed Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 16:01:51 +0200 Subject: [PATCH 269/355] Release 4.0.0-beta.0 --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d81f9a18..c920528c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Change Log +## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.0.0-beta.0) + +**Added** +- [SDK-3159] JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3244] Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3226] Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Improved README structure [\#571](https://github.com/auth0/java-jwt/pull/571) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3154] Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3155] Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) +- Add lint checks [\#561](https://github.com/auth0/java-jwt/pull/561) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3149] Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) +- Testing Java LTS versions [\#536](https://github.com/auth0/java-jwt/pull/536) ([poovamraj](https://github.com/poovamraj)) + +**Changed** +- [SDK-3158] Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3151] Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3151] Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3197] Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) + +**Deprecated** +- [SDK-3192] Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) + +**Removed** +- Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3160] Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Test only change - remove unnecessary throws clause from tests [\#535](https://github.com/auth0/java-jwt/pull/535) ([jimmyjames](https://github.com/jimmyjames)) + +**Security** +- [SDK-3125] Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) + +**Breaking changes** +- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) +- Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- [SDK-3171] Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3150] Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3160] Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) + ## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) From a14643d4a9cca096d3dc2049db6a4e0522d454d9 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 6 May 2022 16:10:37 +0200 Subject: [PATCH 270/355] Updated Changelog for beta --- CHANGELOG.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c920528c..123788f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,53 @@ # Change Log ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) -[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.0.0-beta.0) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) + +💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** -- [SDK-3159] JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3244] Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3226] Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) +- Add Migration Guide [\#576](https://github.com/auth0/java-jwt/pull/576) ([jimmyjames](https://github.com/jimmyjames)) +- Expose claim name and header constants [\#574](https://github.com/auth0/java-jwt/pull/574) ([jimmyjames](https://github.com/jimmyjames)) +- Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) - Improved README structure [\#571](https://github.com/auth0/java-jwt/pull/571) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3154] Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3155] Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) +- Improved Exception Handling [\#568](https://github.com/auth0/java-jwt/pull/568) ([poovamraj](https://github.com/poovamraj)) +- Predicate based Claim verification [\#562](https://github.com/auth0/java-jwt/pull/562) ([poovamraj](https://github.com/poovamraj)) - Add lint checks [\#561](https://github.com/auth0/java-jwt/pull/561) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3149] Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) +- Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Add Instant support [\#537](https://github.com/auth0/java-jwt/pull/537) ([jimmyjames](https://github.com/jimmyjames)) - Testing Java LTS versions [\#536](https://github.com/auth0/java-jwt/pull/536) ([poovamraj](https://github.com/poovamraj)) **Changed** -- [SDK-3158] Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3151] Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3151] Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3197] Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) +- Null claim handling [\#564](https://github.com/auth0/java-jwt/pull/564) ([poovamraj](https://github.com/poovamraj)) +- Undeprecate Single Key Constructor for Algorithms [\#551](https://github.com/auth0/java-jwt/pull/551) ([poovamraj](https://github.com/poovamraj)) +- Update documentation and undeprecate single content sign methods [\#550](https://github.com/auth0/java-jwt/pull/550) ([poovamraj](https://github.com/poovamraj)) +- Update test deps [\#539](https://github.com/auth0/java-jwt/pull/539) ([jimmyjames](https://github.com/jimmyjames)) **Deprecated** -- [SDK-3192] Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) +- Deprecate secp256k1 curve for EC Algorithms [\#540](https://github.com/auth0/java-jwt/pull/540) ([poovamraj](https://github.com/poovamraj)) **Removed** - Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3160] Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) +- Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- Remove internal Clock [\#533](https://github.com/auth0/java-jwt/pull/533) ([jimmyjames](https://github.com/jimmyjames)) **Fixed** - Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3186] Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) +- Support date/time custom claim validation [\#538](https://github.com/auth0/java-jwt/pull/538) ([jimmyjames](https://github.com/jimmyjames)) - Test only change - remove unnecessary throws clause from tests [\#535](https://github.com/auth0/java-jwt/pull/535) ([jimmyjames](https://github.com/jimmyjames)) **Security** -- [SDK-3125] Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) +- Updated documentation regarding HMAC Key length [\#580](https://github.com/auth0/java-jwt/pull/580) ([poovamraj](https://github.com/poovamraj)) **Breaking changes** -- [SDK-3231] Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) +- Added support for multiple checks on a single claim [\#573](https://github.com/auth0/java-jwt/pull/573) ([poovamraj](https://github.com/poovamraj)) - Improve keyprovider reliability [\#570](https://github.com/auth0/java-jwt/pull/570) ([poovamraj](https://github.com/poovamraj)) - Remove ES256K support [\#556](https://github.com/auth0/java-jwt/pull/556) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3212] Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) -- [SDK-3171] Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3150] Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) -- [SDK-3160] Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) +- Remove impl package export in module-info [\#553](https://github.com/auth0/java-jwt/pull/553) ([poovamraj](https://github.com/poovamraj)) +- Fix header claims serialization [\#549](https://github.com/auth0/java-jwt/pull/549) ([jimmyjames](https://github.com/jimmyjames)) +- Serialize dates in collections as seconds since epoch [\#534](https://github.com/auth0/java-jwt/pull/534) ([jimmyjames](https://github.com/jimmyjames)) +- Replace com.auth0.jwt.interfaces.Clock with java.time.Clock [\#532](https://github.com/auth0/java-jwt/pull/532) ([jimmyjames](https://github.com/jimmyjames)) ## [3.19.2](https://github.com/auth0/java-jwt/tree/3.19.2) (2022-05-05) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.1...3.19.2) From 90a68cbeba433c1634450f7d4d32e882aec5bd79 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 11:59:21 +0200 Subject: [PATCH 271/355] Release 4.0.0 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 123788f0..6f330308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Change Log +## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0-beta.0...4.0.0) + ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +? Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From b786c9dd42c2332c3caaca3b37198e8025c4b912 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 12:16:27 +0200 Subject: [PATCH 272/355] Update release note in CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f330308..57edfa51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,24 @@ # Change Log ## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) -[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0-beta.0...4.0.0) +[Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0) + +**This is a major release and contains breaking changes!** + +- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. + +### Main features +- Predicates based claim verification +- Support for Instant API and Lambda functions +- Improved Exceptions API +- Consistent null handling + +See the changelog entries for additional details. ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -? Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From 84dc66063790905561e3bb2addffcd5bc7125697 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 12:19:12 +0200 Subject: [PATCH 273/355] Fix emoji used in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57edfa51..6023d836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ See the changelog entries for additional details. ## [4.0.0-beta.0](https://github.com/auth0/java-jwt/tree/4.0.0-beta.0) (2022-05-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0-beta.0) -- Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. +💡 Check the [Migration Guide](https://github.com/auth0/java-jwt/blob/master/MIGRATION_GUIDE.md) to understand the changes required to migrate your application to v4. **Added** - JavaDoc updated [\#577](https://github.com/auth0/java-jwt/pull/577) ([poovamraj](https://github.com/poovamraj)) From 929b99ed71c086b044e9f38fb6996298b604d58a Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 24 Jun 2022 13:22:48 +0200 Subject: [PATCH 274/355] Update README.md --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f649a742..77fb30fc 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:3.19.0' +implementation 'com.auth0:java-jwt:4.0.0' ``` ### Maven @@ -48,16 +48,10 @@ implementation 'com.auth0:java-jwt:3.19.0' com.auth0 java-jwt - 3.19.2 + 4.0.0 ``` -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:3.19.2' -``` - ## Available Algorithms The library implements JWT Verification and Signing using the following algorithms: From 3e6f4b80a125df67a97df6d8eadf675d79907624 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 5 Jul 2022 17:12:01 -0400 Subject: [PATCH 275/355] Create semgrep.yml --- .github/workflows/semgrep.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000..e0227e37 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +name: Semgrep + +on: + pull_request: {} + + push: + branches: ["master", "main"] + + schedule: + - cron: '30 0 1,15 * *' + +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + container: + image: returntocorp/semgrep + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/checkout@v3 + + - run: semgrep ci + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} From 190132ca282c2b6ff2d12b04c1ea07224ad9f271 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Fri, 8 Jul 2022 00:24:46 -0500 Subject: [PATCH 276/355] Update config.yml --- .circleci/config.yml | 48 +++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f47d6efa..c6425bb8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,6 @@ version: 2.1 +orbs: + codecov: codecov/codecov@3 commands: checkout-and-build: @@ -8,9 +10,9 @@ commands: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- - run: ./gradlew clean build - save_cache: paths: @@ -19,10 +21,7 @@ commands: run-tests: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - - run: - name: Upload Coverage - when: on_success - command: bash <(curl -s https://codecov.io/bash) -Z -C $CIRCLE_SHA1 + - codecov/upload # TODO re-enable once 4.0.0 is released # run-api-diff: # steps: @@ -43,36 +42,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb -# api-diff: -# docker: -# - image: openjdk:11.0-jdk -# steps: -# - checkout-and-build -# - run-api-diff -# environment: -# GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' -# _JAVA_OPTIONS: "-Xms512m -Xmx1024m" -# TERM: dumb - semgrep: - docker: - - image: returntocorp/semgrep-agent:v1 - environment: - SEMGREP_REPO_NAME: "auth0/java-jwt" - SEMGREP_REPO_URL: "https://github.com/auth0/java-jwt" - steps: - - checkout - - run: - name: Run vulnerabilities tests (Semgrep) - command: | - semgrep-agent --baseline-ref master --publish-token $SEMGREP_TOKEN + # api-diff: + # docker: + # - image: openjdk:11.0-jdk + # steps: + # - checkout-and-build + # - run-api-diff + # environment: + # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + # TERM: dumb workflows: build-and-test: jobs: - build - - semgrep: - context: - - semgrep-env # api-diff: # jobs: # - api-diff From 8dc5fbb6cde11084c9c74096d39f869910733bb8 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 11 Jul 2022 15:29:08 +0200 Subject: [PATCH 277/355] reenable api-diff check for 4.0.0 --- .circleci/config.yml | 43 +++++++++++++++++++++---------------------- lib/build.gradle | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c6425bb8..8532ec75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,15 +22,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload -# TODO re-enable once 4.0.0 is released -# run-api-diff: -# steps: -# # run apiDiff task -# - run: ./gradlew apiDiff -# - store_artifacts: -# path: lib/build/reports/apiDiff/apiDiff.txt -# - store_artifacts: -# path: lib/build/reports/apiDiff/apiDiff.html + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: @@ -42,21 +41,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - # api-diff: - # docker: - # - image: openjdk:11.0-jdk - # steps: - # - checkout-and-build - # - run-api-diff - # environment: - # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - # TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: jobs: - build -# api-diff: -# jobs: -# - api-diff + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index b1b24889..01a5adbe 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -17,7 +17,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "3.18.2" + baselineCompareVersion "4.0.0" developers { auth0 { From f30a47b917478010aa91a2093f9db786ef9a7e32 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 11 Jul 2022 15:44:38 +0200 Subject: [PATCH 278/355] Update config.yml --- .circleci/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8532ec75..1675c39f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,16 +41,16 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: From 0bf73a60152ff86014d6687c336851fe78190aba Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Fri, 22 Jul 2022 10:59:13 +0200 Subject: [PATCH 279/355] Provide straightforward example for JWKS --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77fb30fc..822e9db7 100644 --- a/README.md +++ b/README.md @@ -199,10 +199,10 @@ By using a `KeyProvider` you can change in runtime the key used either to verify - `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. -The following example shows how this would work with `JwkStore`, an imaginary [JWK Set](https://auth0.com/docs/jwks) implementation. For simple key rotation using JWKS, try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. +The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. ```java -final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); +final JwkProvider jwkStore = new UrlJwkProvider("https://samples.auth0.com/"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key @@ -210,7 +210,7 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header - RSAPublicKey publicKey = jwkStore.get(kid); + PublicKey publicKey = jwkStore.get(kid).getPublicKey(); return (RSAPublicKey) publicKey; } From 81c8b46b8974bd2160218c2b5116e6d4dfae4a61 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 25 Jul 2022 11:02:43 +0200 Subject: [PATCH 280/355] Updated variable name for README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 822e9db7..bbc166c1 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ By using a `KeyProvider` you can change in runtime the key used either to verify The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. ```java -final JwkProvider jwkStore = new UrlJwkProvider("https://samples.auth0.com/"); +final JwkProvider jwkProvider = new UrlJwkProvider("https://samples.auth0.com/"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key @@ -210,7 +210,7 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header - PublicKey publicKey = jwkStore.get(kid).getPublicKey(); + PublicKey publicKey = jwkProvider.get(kid).getPublicKey(); return (RSAPublicKey) publicKey; } From c36ec6ff80c0d62668a5ae726f9198d3ca587a17 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Mon, 22 Aug 2022 10:51:52 +0200 Subject: [PATCH 281/355] Make JWT constants final values --- lib/src/main/java/com/auth0/jwt/HeaderParams.java | 8 ++++---- .../main/java/com/auth0/jwt/RegisteredClaims.java | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/HeaderParams.java b/lib/src/main/java/com/auth0/jwt/HeaderParams.java index 7af3c442..1107f313 100644 --- a/lib/src/main/java/com/auth0/jwt/HeaderParams.java +++ b/lib/src/main/java/com/auth0/jwt/HeaderParams.java @@ -10,20 +10,20 @@ private HeaderParams() {} /** * The algorithm used to sign a JWT. */ - public static String ALGORITHM = "alg"; + public static final String ALGORITHM = "alg"; /** * The content type of the JWT. */ - public static String CONTENT_TYPE = "cty"; + public static final String CONTENT_TYPE = "cty"; /** * The media type of the JWT. */ - public static String TYPE = "typ"; + public static final String TYPE = "typ"; /** * The key ID of a JWT used to specify the key for signature validation. */ - public static String KEY_ID = "kid"; + public static final String KEY_ID = "kid"; } diff --git a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java index 8c47eb51..c5509716 100644 --- a/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java +++ b/lib/src/main/java/com/auth0/jwt/RegisteredClaims.java @@ -13,43 +13,43 @@ private RegisteredClaims() { * The "iss" (issuer) claim identifies the principal that issued the JWT. * Refer RFC 7529 Section 4.1.1 */ - public static String ISSUER = "iss"; + public static final String ISSUER = "iss"; /** * The "sub" (subject) claim identifies the principal that is the subject of the JWT. * Refer RFC 7529 Section 4.1.2 */ - public static String SUBJECT = "sub"; + public static final String SUBJECT = "sub"; /** * The "aud" (audience) claim identifies the recipients that the JWT is intended for. * Refer RFC 7529 Section 4.1.3 */ - public static String AUDIENCE = "aud"; + public static final String AUDIENCE = "aud"; /** * The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be * accepted for processing. * Refer RFC 7529 Section 4.1.4 */ - public static String EXPIRES_AT = "exp"; + public static final String EXPIRES_AT = "exp"; /** * The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. * Refer RFC 7529 Section 4.1.5 */ - public static String NOT_BEFORE = "nbf"; + public static final String NOT_BEFORE = "nbf"; /** * The "iat" (issued at) claim identifies the time at which the JWT was issued. * Refer RFC 7529 Section 4.1.6 */ - public static String ISSUED_AT = "iat"; + public static final String ISSUED_AT = "iat"; /** * The "jti" (JWT ID) claim provides a unique identifier for the JWT. * Refer RFC 7529 Section 4.1.7 */ - public static String JWT_ID = "jti"; + public static final String JWT_ID = "jti"; } From 15202989d733ce695c302d2f2029bd67c919fd99 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 29 Aug 2022 19:32:23 -0500 Subject: [PATCH 282/355] [SDK-3816] Update docs for verification thread-safety --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 6 +++++- .../java/com/auth0/jwt/interfaces/JWTVerifier.java | 14 +++++++++++++- .../com/auth0/jwt/interfaces/Verification.java | 4 +++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 0bc17fb1..07c86a4c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -46,7 +46,11 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException { } /** - * {@link Verification} implementation that accepts all the expected Claim values for verification. + * {@link Verification} implementation that accepts all the expected Claim values for verification, and + * builds a {@link com.auth0.jwt.interfaces.JWTVerifier} used to verify a JWT's signature and expected claims. + * + * Note that this class is not thread-safe. Calling {@link #build()} returns an instance of + * {@link com.auth0.jwt.interfaces.JWTVerifier} which can be reused. */ public static class BaseVerification implements Verification { private final Algorithm algorithm; diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java index b7030a97..2756ddd8 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/JWTVerifier.java @@ -4,7 +4,19 @@ /** - * Used to verify the JWT for its signature and claims. + * Used to verify the JWT for its signature and claims. Implementations must be thread-safe. Instances are created + * using {@link Verification}. + * + *
+ * try {
+ *      JWTVerifier verifier = JWTVerifier.init(Algorithm.RSA256(publicKey, privateKey)
+ *          .withIssuer("auth0")
+ *          .build();
+ *      DecodedJWT jwt = verifier.verify("token");
+ * } catch (JWTVerificationException e) {
+ *      // invalid signature or claims
+ * }
+ * 
*/ public interface JWTVerifier { diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 4a8a0f84..b4adcf5c 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -7,7 +7,9 @@ import java.util.function.BiPredicate; /** - * Constructs and holds the checks required for a JWT to be considered valid. + * Constructs and holds the checks required for a JWT to be considered valid. Note that implementations are + * not thread-safe. Once built by calling {@link #build()}, the resulting + * {@link com.auth0.jwt.interfaces.JWTVerifier} is thread-safe. */ public interface Verification { From f530b268354926792461926a0eb8d810be01583b Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 29 Aug 2022 20:11:33 -0500 Subject: [PATCH 283/355] Check for null token before splitting --- lib/src/main/java/com/auth0/jwt/TokenUtils.java | 3 +++ lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index 0a76028b..6fa731c5 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -12,6 +12,9 @@ abstract class TokenUtils { * @throws JWTDecodeException if the Token doesn't have 3 parts. */ static String[] splitToken(String token) throws JWTDecodeException { + if (token == null) { + throw new JWTDecodeException("The token is null."); + } String[] parts = token.split("\\."); if (parts.length == 2 && token.endsWith(".")) { //Tokens with alg='none' have empty String as Signature. diff --git a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java index d32ee859..8649ec90 100644 --- a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java +++ b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java @@ -52,4 +52,11 @@ public void shouldThrowOnSplitTokenWithLessThan3Parts() { String token = "two.parts"; TokenUtils.splitToken(token); } + + @Test + public void shouldThrowOnSplitTokenWithNullValue() { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The token is null."); + TokenUtils.splitToken(null); + } } \ No newline at end of file From 39eec490bd9ad07f7260e9fa1fd0cf60369c6036 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:34:11 +0200 Subject: [PATCH 284/355] Trigger Build From 122a8af5c09b72a6fd22185970031c1a5b3e6961 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:52:11 +0200 Subject: [PATCH 285/355] Disable API diff task temporarily --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1675c39f..fb185917 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,14 +22,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload - run-api-diff: - steps: - # run apiDiff task - - run: ./gradlew apiDiff - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.txt - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.html + # run-api-diff: + # steps: + # # run apiDiff task + # - run: ./gradlew apiDiff + # - store_artifacts: + # path: lib/build/reports/apiDiff/apiDiff.txt + # - store_artifacts: + # path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: From 89fbc61318641b88e3b8b400ff4e8a67d04632fa Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 31 Aug 2022 15:56:09 +0200 Subject: [PATCH 286/355] Disable API diff --- .circleci/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fb185917..73f58ddb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,21 +41,21 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb + # api-diff: + # docker: + # - image: openjdk:11.0-jdk + # steps: + # - checkout-and-build + # - run-api-diff + # environment: + # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + # TERM: dumb workflows: build-and-test: jobs: - build - api-diff: - jobs: - - api-diff + # api-diff: + # jobs: + # - api-diff From 884ac926d7f7afb7e42036a65dc739e9bed79e25 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 7 Sep 2022 10:22:13 -0500 Subject: [PATCH 287/355] Update OSS plugin to latest --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index b614415b..a4cda2d7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,7 +3,7 @@ pluginManagement { gradlePluginPortal() } plugins { - id 'com.auth0.gradle.oss-library.java' version '0.16.0' + id 'com.auth0.gradle.oss-library.java' version '0.17.2' } } From f29d70c56768bd7ed8356a44040082087e076cd8 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 8 Sep 2022 21:41:03 -0500 Subject: [PATCH 288/355] Update to gradle 6.9.2 --- gradle/wrapper/gradle-wrapper.properties | 2 +- lib/build.gradle | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d9ca164..ec991f9a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lib/build.gradle b/lib/build.gradle index 01a5adbe..eab6a0a5 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -44,7 +44,7 @@ java { compileJava { exclude 'module-info.java' // Required to be compatible with JDK 8+ - options.compilerArgs = ['--release', "8"] + options.release = 8 } javadoc { @@ -99,7 +99,8 @@ task compileModuleInfoJava(type: JavaCompile) { } compileTestJava { - options.compilerArgs = ['--release', "8", "-Xlint:deprecation"] + options.release = 8 + options.compilerArgs = ["-Xlint:deprecation"] } def testJava8 = tasks.register('testJava8', Test) { From adbabeb7c45fc3862a5d5b2c64321d06a33c725d Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 19 Sep 2022 08:17:15 -0500 Subject: [PATCH 289/355] Add Ship CLI support --- .shiprc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .shiprc diff --git a/.shiprc b/.shiprc new file mode 100644 index 00000000..7509d078 --- /dev/null +++ b/.shiprc @@ -0,0 +1,6 @@ +{ + "files": { + "README.md": [] + }, + "prefixVersion": false +} \ No newline at end of file From 75a0bc0f9f0411891bb0e791e13dfa91a01e38ac Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Wed, 5 Oct 2022 14:52:37 +0200 Subject: [PATCH 290/355] Add integration with our Shipping orb --- .circleci/config.yml | 12 ++++++++++++ .shiprc | 3 ++- lib/build.gradle | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 73f58ddb..4741868d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,6 @@ version: 2.1 orbs: + ship: auth0/ship@0.7.1 codecov: codecov/codecov@3 commands: @@ -56,6 +57,17 @@ workflows: build-and-test: jobs: - build + - ship/java-publish: + prefix-tag: false + context: + - publish-gh + - publish-sonatype + filters: + branches: + only: + - master + requires: + - build # api-diff: # jobs: # - api-diff diff --git a/.shiprc b/.shiprc index 7509d078..a00fb91e 100644 --- a/.shiprc +++ b/.shiprc @@ -1,6 +1,7 @@ { "files": { - "README.md": [] + "README.md": [], + "lib/build.gradle": [] }, "prefixVersion": false } \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index eab6a0a5..74cdb5c1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,3 +1,7 @@ +buildscript { + version = "4.0.0" +} + plugins { id 'java' id 'jacoco' @@ -5,6 +9,13 @@ plugins { id 'checkstyle' } +def signingKey = findProperty('SIGNING_KEY') +def signingKeyPwd = findProperty('SIGNING_PASSWORD') + +signing { + useInMemoryPgpKeys(signingKey, signingKeyPwd) +} + checkstyle { toolVersion '10.0' checkstyleTest.enabled = false //We are disabling lint checks for tests @@ -134,3 +145,11 @@ jar { compileModuleInfoJava.dependsOn compileJava classes.dependsOn compileModuleInfoJava + +// Creates a version.txt file containing the current version of the SDK. +// This file is picked up and parsed by our Ship Orb to determine the version. +task exportVersion() { + doLast { + new File(rootDir, "version.txt").text = "$version" + } +} From d2bf8d35e96ea8233f2a80ca6d7371ad54e5ba18 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 6 Oct 2022 13:47:56 -0500 Subject: [PATCH 291/355] Release 4.1.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6023d836..04796fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log +## [4.1.0](https://github.com/auth0/java-jwt/tree/4.1.0) (2022-10-06) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.1.0) + +**⚠️ BREAKING CHANGES** +- Make JWT constants final values [\#604](https://github.com/auth0/java-jwt/pull/604) ([poovamraj](https://github.com/poovamraj)) + +**Added** +- Add integration with our Shipping orb [\#612](https://github.com/auth0/java-jwt/pull/612) ([frederikprijck](https://github.com/frederikprijck)) +- Add Ship CLI support [\#609](https://github.com/auth0/java-jwt/pull/609) ([jimmyjames](https://github.com/jimmyjames)) +- Provide straightforward example for JWKS [\#600](https://github.com/auth0/java-jwt/pull/600) ([poovamraj](https://github.com/poovamraj)) + +**Changed** +- Update to gradle 6.9.2 [\#608](https://github.com/auth0/java-jwt/pull/608) ([jimmyjames](https://github.com/jimmyjames)) +- Update OSS plugin to latest [\#607](https://github.com/auth0/java-jwt/pull/607) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3466] Upgrade Codecov [\#595](https://github.com/auth0/java-jwt/pull/595) ([evansims](https://github.com/evansims)) +- Update README.md [\#590](https://github.com/auth0/java-jwt/pull/590) ([poovamraj](https://github.com/poovamraj)) + +**Fixed** +- Check for null token before splitting [\#606](https://github.com/auth0/java-jwt/pull/606) ([jimmyjames](https://github.com/jimmyjames)) +- [SDK-3816] Update docs for verification thread-safety [\#605](https://github.com/auth0/java-jwt/pull/605) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.0.0](https://github.com/auth0/java-jwt/tree/4.0.0) (2022-06-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/3.19.2...4.0.0) diff --git a/README.md b/README.md index bbc166c1..a4fa509e 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.0.0' +implementation 'com.auth0:java-jwt:4.1.0' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.0.0' com.auth0 java-jwt - 4.0.0 + 4.1.0 ``` diff --git a/lib/build.gradle b/lib/build.gradle index 74cdb5c1..8ec1b630 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.0.0" + version = "4.1.0" } plugins { From 9c708c2ca0a9c7a6ab7b900fcf4292d0654a78ea Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:08:49 +0200 Subject: [PATCH 292/355] Update config.yml --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4741868d..505c6f70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.1 + ship: auth0/ship@dev:a88c4d7 codecov: codecov/codecov@3 commands: @@ -66,6 +66,7 @@ workflows: branches: only: - master + - automated-release requires: - build # api-diff: From 7cac89a50bafa65cafebbc919d8b18616b173853 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:19:03 +0200 Subject: [PATCH 293/355] Update config.yml --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 505c6f70..b431f8d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,6 +59,7 @@ workflows: - build - ship/java-publish: prefix-tag: false + package-name: java-jwt context: - publish-gh - publish-sonatype From 245c019bd20bb479b68f220d576e5ad78cae9779 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:23:39 +0200 Subject: [PATCH 294/355] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b431f8d7..10a12231 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:a88c4d7 + ship: auth0/ship@dev:9412fdc codecov: codecov/codecov@3 commands: From bd9c8133025c9da93ce7d5f8885fea43b75c2421 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Mon, 10 Oct 2022 21:35:27 +0200 Subject: [PATCH 295/355] Update config.yml --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 10a12231..6a313263 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:9412fdc + ship: auth0/ship@dev:d4bf2df codecov: codecov/codecov@3 commands: @@ -59,7 +59,6 @@ workflows: - build - ship/java-publish: prefix-tag: false - package-name: java-jwt context: - publish-gh - publish-sonatype From 6a666b0c822d8a453405e67f702c0b326d2d8a5c Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Tue, 11 Oct 2022 12:05:04 +0200 Subject: [PATCH 296/355] update build.gradle file --- lib/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 8ec1b630..d7951ff3 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -9,8 +9,8 @@ plugins { id 'checkstyle' } -def signingKey = findProperty('SIGNING_KEY') -def signingKeyPwd = findProperty('SIGNING_PASSWORD') +def signingKey = findProperty('signingKey') +def signingKeyPwd = findProperty('signingPassword') signing { useInMemoryPgpKeys(signingKey, signingKeyPwd) @@ -29,6 +29,7 @@ oss { organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.0.0" + skipAssertSigningConfiguration true developers { auth0 { @@ -66,6 +67,7 @@ javadoc { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' testImplementation 'net.jodah:concurrentunit:0.4.6' From 4fbe13f8da785c4826cf5b8fd4c3fec708cdda89 Mon Sep 17 00:00:00 2001 From: frederikprijck Date: Tue, 11 Oct 2022 12:18:20 +0200 Subject: [PATCH 297/355] revert changes --- .circleci/config.yml | 1 - lib/build.gradle | 1 - 2 files changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a313263..23f09bb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,6 @@ workflows: branches: only: - master - - automated-release requires: - build # api-diff: diff --git a/lib/build.gradle b/lib/build.gradle index d7951ff3..3649399c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -67,7 +67,6 @@ javadoc { dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' testImplementation 'net.jodah:concurrentunit:0.4.6' From a2ecb759b2b0200f5a61fe9227b5a2ddf804eee7 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Tue, 11 Oct 2022 15:16:41 +0200 Subject: [PATCH 298/355] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23f09bb2..e6b1dcd9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@dev:d4bf2df + ship: auth0/ship@0.7.2 codecov: codecov/codecov@3 commands: From 1e9072d5b0f762f8fe83c6674cd9a3a77bc83255 Mon Sep 17 00:00:00 2001 From: noetro Date: Sat, 15 Oct 2022 03:34:26 +0200 Subject: [PATCH 299/355] Optimise TokenUtils parsing (#611) * Optimise parsing of token for well-defined JWT format * Update error message in test to match new code * Fixing checkstyle issues * Added missing test case for no parts * Return new JWTDecodeException Return a new JWTDecodeException from private utility method `wrongNumberOfParts`, instead of throwing, since we throw from `splitToken()`. Co-authored-by: Jim Anderson Co-authored-by: Jim Anderson --- .../main/java/com/auth0/jwt/TokenUtils.java | 32 ++++++++++++---- .../java/com/auth0/jwt/JWTDecoderTest.java | 2 +- .../java/com/auth0/jwt/TokenUtilsTest.java | 38 +++++++++++++++++-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/TokenUtils.java b/lib/src/main/java/com/auth0/jwt/TokenUtils.java index 6fa731c5..e62b9832 100644 --- a/lib/src/main/java/com/auth0/jwt/TokenUtils.java +++ b/lib/src/main/java/com/auth0/jwt/TokenUtils.java @@ -15,15 +15,33 @@ static String[] splitToken(String token) throws JWTDecodeException { if (token == null) { throw new JWTDecodeException("The token is null."); } - String[] parts = token.split("\\."); - if (parts.length == 2 && token.endsWith(".")) { - //Tokens with alg='none' have empty String as Signature. - parts = new String[]{parts[0], parts[1], ""}; + + char delimiter = '.'; + + int firstPeriodIndex = token.indexOf(delimiter); + if (firstPeriodIndex == -1) { + throw wrongNumberOfParts(0); } - if (parts.length != 3) { - throw new JWTDecodeException( - String.format("The token was expected to have 3 parts, but got %s.", parts.length)); + + int secondPeriodIndex = token.indexOf(delimiter, firstPeriodIndex + 1); + if (secondPeriodIndex == -1) { + throw wrongNumberOfParts(2); + } + + // too many ? + if (token.indexOf(delimiter, secondPeriodIndex + 1) != -1) { + throw wrongNumberOfParts("> 3"); } + + String[] parts = new String[3]; + parts[0] = token.substring(0, firstPeriodIndex); + parts[1] = token.substring(firstPeriodIndex + 1, secondPeriodIndex); + parts[2] = token.substring(secondPeriodIndex + 1); + return parts; } + + private static JWTDecodeException wrongNumberOfParts(Object partCount) { + return new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", partCount)); + } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java index 82dc895c..cc427d60 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java @@ -50,7 +50,7 @@ public void shouldThrowIfLessThan3Parts() { @Test public void shouldThrowIfMoreThan3Parts() { exception.expect(JWTDecodeException.class); - exception.expectMessage("The token was expected to have 3 parts, but got 4."); + exception.expectMessage("The token was expected to have 3 parts, but got > 3."); JWT.decode("this.has.four.parts"); } diff --git a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java index 8649ec90..01806ddc 100644 --- a/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java +++ b/lib/src/test/java/com/auth0/jwt/TokenUtilsTest.java @@ -13,6 +13,30 @@ public class TokenUtilsTest { @Rule public ExpectedException exception = ExpectedException.none(); + @Test + public void toleratesEmptyFirstPart() { + String token = ".eyJpc3MiOiJhdXRoMCJ9.W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; + String[] parts = TokenUtils.splitToken(token); + + assertThat(parts, is(notNullValue())); + assertThat(parts, is(arrayWithSize(3))); + assertThat(parts[0], is("")); + assertThat(parts[1], is("eyJpc3MiOiJhdXRoMCJ9")); + assertThat(parts[2], is("W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc")); + } + + @Test + public void toleratesEmptySecondPart() { + String token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0..W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; + String[] parts = TokenUtils.splitToken(token); + + assertThat(parts, is(notNullValue())); + assertThat(parts, is(arrayWithSize(3))); + assertThat(parts[0], is("eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0")); + assertThat(parts[1], is("")); + assertThat(parts[2], is("W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc")); + } + @Test public void shouldSplitToken() { String token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoMCJ9.W1mx_Y0hbAMbPmfW9whT605AAcxB7REFuJiDAHk2Sdc"; @@ -34,19 +58,27 @@ public void shouldSplitTokenWithEmptySignature() { assertThat(parts, is(arrayWithSize(3))); assertThat(parts[0], is("eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0")); assertThat(parts[1], is("eyJpc3MiOiJhdXRoMCJ9")); - assertThat(parts[2], is(isEmptyString())); + assertThat(parts[2], is(emptyString())); } @Test public void shouldThrowOnSplitTokenWithMoreThan3Parts() { exception.expect(JWTDecodeException.class); - exception.expectMessage("The token was expected to have 3 parts, but got 4."); + exception.expectMessage("The token was expected to have 3 parts, but got > 3."); String token = "this.has.four.parts"; TokenUtils.splitToken(token); } @Test - public void shouldThrowOnSplitTokenWithLessThan3Parts() { + public void shouldThrowOnSplitTokenWithNoParts() { + exception.expect(JWTDecodeException.class); + exception.expectMessage("The token was expected to have 3 parts, but got 0."); + String token = "notajwt"; + TokenUtils.splitToken(token); + } + + @Test + public void shouldThrowOnSplitTokenWith2Parts() { exception.expect(JWTDecodeException.class); exception.expectMessage("The token was expected to have 3 parts, but got 2."); String token = "two.parts"; From 9840e74b7ef87ce9678d2ee71308499fb1b6f757 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 14 Oct 2022 20:37:24 -0500 Subject: [PATCH 300/355] Update Claim#asString documentation (#615) --- lib/src/main/java/com/auth0/jwt/interfaces/Claim.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java index 7c4da4f7..ca5244d6 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Claim.java @@ -62,9 +62,10 @@ public interface Claim { /** * Get this Claim as a String. - * If the value isn't of type String or it can't be converted to a String, {@code null} will be returned. + * If the value isn't of type String, {@code null} will be returned. For a String representation of non-textual + * claim types, clients can call {@code toString()}. * - * @return the value as a String or null. + * @return the value as a String or null if the underlying value is not a string. */ String asString(); From 1875ed74b136422952080b5f2ac7656e67e51f27 Mon Sep 17 00:00:00 2001 From: "sre-57-opslevel[bot]" <113727212+sre-57-opslevel[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:07:35 +0000 Subject: [PATCH 301/355] Upload OpsLevel YAML --- opslevel.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 opslevel.yml diff --git a/opslevel.yml b/opslevel.yml new file mode 100644 index 00000000..009a5ec0 --- /dev/null +++ b/opslevel.yml @@ -0,0 +1,6 @@ +--- +version: 1 +repository: + owner: dx_sdks + tier: + tags: From e8808abb910bb2d3ea8da816e626a7df9fc543e9 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 10:25:34 -0500 Subject: [PATCH 302/355] Update .shiprc to only update lib version in build.gradle (#625) --- .shiprc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.shiprc b/.shiprc index a00fb91e..2bd0fdb3 100644 --- a/.shiprc +++ b/.shiprc @@ -1,7 +1,7 @@ { "files": { "README.md": [], - "lib/build.gradle": [] + "lib/build.gradle": ["version[[:blank:]]*=[[:blank:]]*{MAJOR}.{MINOR}.{PATCH}"] }, "prefixVersion": false } \ No newline at end of file From 49c5def67ab2d37aa98f210bec2ca5ea39889d8c Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 10:46:57 -0500 Subject: [PATCH 303/355] Re-enable japicmp API diff checking (#619) --- .circleci/config.yml | 42 +++++++++++++++++++++--------------------- lib/build.gradle | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6b1dcd9..32e9d4de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,14 +23,14 @@ commands: steps: - run: ./gradlew check jacocoTestReport --continue --console=plain - codecov/upload - # run-api-diff: - # steps: - # # run apiDiff task - # - run: ./gradlew apiDiff - # - store_artifacts: - # path: lib/build/reports/apiDiff/apiDiff.txt - # - store_artifacts: - # path: lib/build/reports/apiDiff/apiDiff.html + run-api-diff: + steps: + # run apiDiff task + - run: ./gradlew apiDiff + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.txt + - store_artifacts: + path: lib/build/reports/apiDiff/apiDiff.html jobs: build: docker: @@ -42,16 +42,16 @@ jobs: GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' _JAVA_OPTIONS: "-Xms512m -Xmx1024m" TERM: dumb - # api-diff: - # docker: - # - image: openjdk:11.0-jdk - # steps: - # - checkout-and-build - # - run-api-diff - # environment: - # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - # _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - # TERM: dumb + api-diff: + docker: + - image: openjdk:11.0-jdk + steps: + - checkout-and-build + - run-api-diff + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + _JAVA_OPTIONS: "-Xms512m -Xmx1024m" + TERM: dumb workflows: build-and-test: @@ -68,6 +68,6 @@ workflows: - master requires: - build - # api-diff: - # jobs: - # - api-diff + api-diff: + jobs: + - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index 3649399c..a0d7a7da 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -28,7 +28,7 @@ oss { repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "4.0.0" + baselineCompareVersion "4.1.0" skipAssertSigningConfiguration true developers { From 3526fcc80ec15038de1a13a2c84d55d2453c835a Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 19 Oct 2022 21:53:07 -0500 Subject: [PATCH 304/355] Release 4.2.0 (#626) --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04796fd7..c6c2d511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [4.2.0](https://github.com/auth0/java-jwt/tree/4.2.0) (2022-10-19) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.1.0...4.2.0) + +**Changed** +- Re-enable japicmp API diff checking [\#619](https://github.com/auth0/java-jwt/pull/619) ([jimmyjames](https://github.com/jimmyjames)) +- Update .shiprc to only update lib version in build.gradle [\#625](https://github.com/auth0/java-jwt/pull/625) ([jimmyjames](https://github.com/jimmyjames)) +- Optimise TokenUtils parsing [\#611](https://github.com/auth0/java-jwt/pull/611) ([noetro](https://github.com/noetro)) +- Update Circle Ship Orb configuration [\#616](https://github.com/auth0/java-jwt/pull/616) ([frederikprijck](https://github.com/frederikprijck)) + +**Fixed** +- Update Claim#asString documentation [\#615](https://github.com/auth0/java-jwt/pull/615) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.1.0](https://github.com/auth0/java-jwt/tree/4.1.0) (2022-10-06) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.0.0...4.1.0) diff --git a/README.md b/README.md index a4fa509e..89b67ad8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.1.0' +implementation 'com.auth0:java-jwt:4.2.0' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.1.0' com.auth0 java-jwt - 4.1.0 + 4.2.0 ``` diff --git a/lib/build.gradle b/lib/build.gradle index a0d7a7da..05807472 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.1.0" + version = "4.2.0" } plugins { From 32935767d3414d1e91feec41ee69159b3d2524eb Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 20 Oct 2022 17:08:35 -0500 Subject: [PATCH 305/355] Fix .shiprc build.gradle version substitution pattern (#627) --- .shiprc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.shiprc b/.shiprc index 2bd0fdb3..fe59345e 100644 --- a/.shiprc +++ b/.shiprc @@ -1,7 +1,7 @@ { "files": { "README.md": [], - "lib/build.gradle": ["version[[:blank:]]*=[[:blank:]]*{MAJOR}.{MINOR}.{PATCH}"] + "lib/build.gradle": ["version = \"{MAJOR}.{MINOR}.{PATCH}\""] }, "prefixVersion": false } \ No newline at end of file From 3d3980de9c0a84b6265de9263647cdbfda7c8a5b Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Sat, 22 Oct 2022 21:02:48 -0500 Subject: [PATCH 306/355] Update ship orb (#629) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 32e9d4de..9cf198c8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.2 + ship: auth0/ship@0.7.3 codecov: codecov/codecov@3 commands: From c8d0ba995543c9cee50abe9f4aa4f9873b29489f Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 24 Oct 2022 09:14:06 -0500 Subject: [PATCH 307/355] Bump `com.fasterxml.jackson.core:jackson-databind` to 2.13.4.2 (#630) Resolves https://www.cve.org/CVERecord?id=CVE-2022-42003 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 05807472..9cc4349c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -65,7 +65,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 3cff66f1a23b51ab85ae4d7c5c1837b90677c75e Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 24 Oct 2022 16:52:15 -0500 Subject: [PATCH 308/355] Use latest ship orb (#634) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cf198c8..6c87574b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - ship: auth0/ship@0.7.3 + ship: auth0/ship@0 codecov: codecov/codecov@3 commands: From 4c2533dbd0bc1aa714ac7d0e291652cdaecdd265 Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Tue, 25 Oct 2022 06:08:09 +0400 Subject: [PATCH 309/355] Remove emoji to fix the broken documentation link (#632) Co-authored-by: Jim Anderson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89b67ad8..f96e806f 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); > Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). -##### :key: HMAC Key Length and Security +##### HMAC Key Length and Security When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. From 1e38286a52936d7ccdae3f457369389ad7474b7a Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Tue, 25 Oct 2022 07:26:20 -0500 Subject: [PATCH 310/355] Release 4.2.1 (#636) --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- lib/build.gradle | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c2d511..ed3706dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [4.2.1](https://github.com/auth0/java-jwt/tree/4.2.1) (2022-10-24) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.0...4.2.1) + +**Security** +- Use latest ship orb [\#634](https://github.com/auth0/java-jwt/pull/634) ([jimmyjames](https://github.com/jimmyjames)) +- Bump `com.fasterxml.jackson.core:jackson-databind` to 2.13.4.2 [\#630](https://github.com/auth0/java-jwt/pull/630) ([evansims](https://github.com/evansims)) + ## [4.2.0](https://github.com/auth0/java-jwt/tree/4.2.0) (2022-10-19) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.1.0...4.2.0) diff --git a/README.md b/README.md index f96e806f..bfa4345d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This library is supported for Java 8, 11, and 17. For issues on non-LTS versions ### Gradle ```gradle -implementation 'com.auth0:java-jwt:4.2.0' +implementation 'com.auth0:java-jwt:4.2.1' ``` ### Maven @@ -48,7 +48,7 @@ implementation 'com.auth0:java-jwt:4.2.0' com.auth0 java-jwt - 4.2.0 + 4.2.1 ``` diff --git a/lib/build.gradle b/lib/build.gradle index 9cc4349c..d6c9b061 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,5 @@ buildscript { - version = "4.2.0" + version = "4.2.1" } plugins { From d0ebc3f50581f3341a9b214f32612926b8b1a201 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Wed, 26 Oct 2022 03:38:39 -0500 Subject: [PATCH 311/355] [SDK-3694] README redesign (#617) --- EXAMPLES.md | 130 +++++++++++++ README.md | 523 ++++++++-------------------------------------------- 2 files changed, 206 insertions(+), 447 deletions(-) create mode 100644 EXAMPLES.md diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 00000000..8a849303 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,130 @@ +# Examples using java-jwt + +* [Inspecting a DecodedJWT](#inspecting-a-decodedjwt) +* [DateTime Claim Validation](#datetime-claim-validation) +* [Using custom claims](#using-custom-claims) +* [Using a KeyProvider](#using-a-keyprovider) + +## Inspecting a DecodedJWT + +The successful verification of a JWT returns a `DecodedJWT`, from which you can obtain its contents. + +```java +DecodedJWT jwt = JWT.require(algorithm) + .build() + .verify("a.b.c"); + +// standard claims can be retrieved through first-class methods +String subject = jwt.getSubject(); +String aud = jwt.getAudience(); +// ... + +// custom claims can also be obtained +String customStringClaim = jwt.getClaim("custom-string-claim").asString(); +``` + +When retrieving custom claims, a [Claim](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/interfaces/Claim.html) is returned, which can then be used to obtain the value depending on the value's underlying type. + +## DateTime Claim Validation + +A JWT token may include DateNumber fields that can be used to validate that: + +* The token was issued in a past date `"iat" < NOW` +* The token hasn't expired yet `"exp" > NOW` +* The token can already be used. `"nbf" < NOW` + +When verifying a JWT, the standard DateTime claims are validated by default. A `JWTVerificationException` is thrown if any of the claim values are invalid. + +To specify a **leeway** in which the JWT should still be considered valid, use the `acceptLeeway()` method in the `JWTVerifier` builder and pass a positive seconds value. This applies to every item listed above. + +```java +JWTVerifier verifier = JWT.require(algorithm) + .acceptLeeway(1) // 1 sec for nbf, iat and exp + .build(); +``` + +You can also specify a custom value for a given DateTime claim and override the default one for only that claim. + +```java +JWTVerifier verifier = JWT.require(algorithm) + .acceptLeeway(1) //1 sec for nbf and iat + .acceptExpiresAt(5) //5 secs for exp + .build(); +``` + +If you need to test this behavior in your application, cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: + +```java +BaseVerification verification = (BaseVerification) JWT.require(algorithm) + .acceptLeeway(1) + .acceptExpiresAt(5); +private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); +JWTVerifier verifier = verification.build(clock); +``` + +## Using custom claims + +### JWT creation +A JWT can be built with custom payload and header claims, by using the `withHeader` and `withClaim` methods. + +```java +String jwt = JWT.create() + .withHeader(headerMap) + .withClaim("string-claim", "string-value") + .withClaim("number-claim", 42) + .withClaim("bool-claim", true) + .withClaim("datetime-claim", Instant.now()) + .sign(algorithm); +``` + +See the [JavaDoc](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/JWTCreator.Builder.html) for all available custom claim methods. + +### JWT verification + +You can also verify a JWT's custom claims: + +```java +JWTVerifier verifier = JWT.require(algorithm) + .withClaim("number-claim", 123) + .withClaimPresence("some-claim-that-just-needs-to-be-present") + .withClaim("predicate-claim", (claim, decodedJWT) -> "custom value".equals(claim.asString())) + .build(); +DecodedJWT jwt = verifier.verify("my.jwt.token"); +``` + +See the [JavaDoc](https://javadoc.io/doc/com.auth0/java-jwt/latest/com/auth0/jwt/JWTVerifier.BaseVerification.html) for all available custom claim verification methods. + +## Using a KeyProvider + +A `KeyProvider` can be used to obtain the keys needed for signing and verifying a JWT. How these keys are constructed are beyond the scope of this library, but the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library provides the ability to obtain the public key from a JWK. +The example below demonstrates this for the RSA algorithm (`ECDSAKeyProvider` can be used for ECDSA). + +```java +JwkProvider provider = new JwkProviderBuilder("https://samples.auth0.com/") + .cached(10, 24, TimeUnit.HOURS) + .rateLimited(10, 1, TimeUnit.MINUTES) + .build(); +final RSAPrivateKey privateKey = // private key +final String privateKeyId = // private key ID + +RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKeyById(String kid) { + return (RSAPublicKey) jwkProvider.get(kid).getPublicKey(); + } + + @Override + public RSAPrivateKey getPrivateKey() { + // return the private key used + return rsaPrivateKey; + } + + @Override + public String getPrivateKeyId() { + return rsaPrivateKeyId; + } +}; + +Algorithm algorithm = Algorithm.RSA256(keyProvider); +//Use the Algorithm to create and verify JWTs. +``` \ No newline at end of file diff --git a/README.md b/README.md index bfa4345d..7786cbaf 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,26 @@ - - -# Java JWT +![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) -[![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org) -[![Javadoc](https://javadoc.io/badge2/com.auth0/java-jwt/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) - -A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519). +[![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org/) +[![Maven Central](https://img.shields.io/maven-central/v/com.auth0/java-jwt.svg?style=flat-square)](https://mvnrepository.com/artifact/com.auth0/java-jwt) +[![javadoc](https://javadoc.io/badge2/com.auth0/auth0/javadoc.svg)](https://javadoc.io/doc/com.auth0/java-jwt) -> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. - -If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library. +:books: [Documentation](#documentation) - :rocket: [Getting Started](#getting-started) - :computer: [API Reference](#api-reference) :speech_balloon: [Feedback](#feedback) -> You are viewing the documentation for the v4 beta release. For the latest stable release, please see the [version 3.x documentation](https://github.com/auth0/java-jwt). +## Documentation +- [Examples](./EXAMPLES.md) - code samples for common java-jwt scenarios. +- [Docs site](https://www.auth0.com/docs) - explore our docs site and learn more about Auth0. -## Table of Contents -- [**Requirements**](#requirements) -- [**Installation**](#installation) -- [**Available Algorithms**](#available-algorithms) -- [**Quickstart**](#quickstart) - + [**Create and Sign a Token**](#create-and-sign-a-token) - + [**Verify a Token**](#verify-a-token) -- [**Usage**](#usage) - + [**Pick the algorithm**](#pick-the-algorithm) - + [**Time Validation**](#time-validation) - + [**Header Claims**](#header-claims) - + [**Payload Claims**](#payload-claims) - + [**Claim Class**](#claim-class) -- [**Javadoc**](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) +## Getting Started -## Requirements +### Requirements -This library is supported for Java 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. +This library is supported for Java LTS versions 8, 11, and 17. For issues on non-LTS versions above 8, consideration will be given on a case-by-case basis. -## Installation +> `java-jwt` is intended for server-side JVM applications. Android applications should use [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android). -### Gradle - -```gradle -implementation 'com.auth0:java-jwt:4.2.1' -``` - -### Maven - -```xml - - com.auth0 - java-jwt - 4.2.1 - -``` - -## Available Algorithms - -The library implements JWT Verification and Signing using the following algorithms: +`java-jwt` supports the following algorithms for both signing and verification: | JWS | Algorithm | Description | | :-------------: | :-------------: | :----- | @@ -70,434 +36,97 @@ The library implements JWT Verification and Signing using the following algorith > Note - Support for ECDSA with curve secp256k1 and SHA-256 (ES256K) has been dropped since it has been [disabled in Java 15](https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219) -## Quickstart - -### Create and Sign a Token - -You'll first need to create a `JWTCreator` instance by calling `JWT.create()`. Use the builder to define the custom Claims your token needs to have. Finally to get the String token call `sign()` and pass the `Algorithm` instance. - -* Example using `HS256` - - ```java - try { - Algorithm algorithm = Algorithm.HMAC256("secret"); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); - } catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. - } - ``` - -* Example using `RS256` - - ```java - RSAPublicKey publicKey = //Get the key instance - RSAPrivateKey privateKey = //Get the key instance - try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - String token = JWT.create() - .withIssuer("auth0") - .sign(algorithm); - } catch (JWTCreationException exception){ - //Invalid Signing configuration / Couldn't convert Claims. - } - ``` - -If a Claim couldn't be converted to JSON or the Key used in the signing process was invalid a `JWTCreationException` will raise. - -### Verify a Token - -You'll first need to create a `JWTVerifier` instance by calling `JWT.require()` and passing the `Algorithm` instance. If you require the token to have specific Claim values, use the builder to define them. The instance returned by the method `build()` is reusable, so you can define it once and use it to verify different tokens. Finally call `verifier.verify()` passing the token. - -* Example using `HS256` - - ```java - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - try { - Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); - } catch (JWTVerificationException exception){ - //Invalid signature/claims - } - ``` - -* Example using `RS256` - - ```java - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - RSAPublicKey publicKey = //Get the key instance - RSAPrivateKey privateKey = //Get the key instance - try { - Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer("auth0") - .build(); //Reusable verifier instance - DecodedJWT jwt = verifier.verify(token); - } catch (JWTVerificationException exception){ - //Invalid signature/claims - } - ``` - -If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will raise. - -
-Need to peek into a JWT without verifying it? (Click to expand) - -### Decode a Token - -> __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `JWTVerifier` as documented above instead. - -```java -String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; -try { - DecodedJWT jwt = JWT.decode(token); -} catch (JWTDecodeException exception){ - //Invalid token -} -``` - -If the token has an invalid syntax or the header or payload are not JSONs, a `JWTDecodeException` will raise. - -
- -## Usage - -### Pick the Algorithm - -The Algorithm defines how a token is signed and verified. It can be instantiated with the raw value of the secret in the case of HMAC algorithms, or the key pairs or `KeyProvider` in the case of RSA and ECDSA algorithms. Once created, the instance is reusable for token signing and verification operations. - -When using RSA or ECDSA algorithms and you just need to **sign** JWTs you can avoid specifying a Public Key by passing a `null` value. The same can be done with the Private Key when you just need to **verify** JWTs. - - -#### Using static secrets or keys: - -```java -//HMAC -Algorithm algorithmHS = Algorithm.HMAC256("secret"); //use more secure key - -//RSA -RSAPublicKey publicKey = //Get the key instance -RSAPrivateKey privateKey = //Get the key instance -Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey); -``` - -> Note: How you obtain or read keys is not in the scope of this library. For an example of how you might implement this, see [this gist](https://gist.github.com/lbalmaceda/9a0c7890c2965826c04119dcfb1a5469). - -##### HMAC Key Length and Security - -When using a Hash-based Message Authentication Code, e.g. HS256 or HS512, in order to comply with the strict requirements of the JSON Web Algorithms (JWA) specification (RFC7518), you **must** use a secret key which has the same (or larger) bit length as the size of the output hash. This is to avoid weakening the security strength of the authentication code (see NIST recommendations NIST SP 800-117). For example, when using HMAC256, the secret key length must be a minimum of 256 bits. - -#### Using a KeyProvider: -By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods: - -- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time). -- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT. -- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`. - - -The following example shows how this would work with `JwkProvider` from the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library. - -```java -final JwkProvider jwkProvider = new UrlJwkProvider("https://samples.auth0.com/"); -final RSAPrivateKey privateKey = //Get the key instance -final String privateKeyId = //Create an Id for the above key - -RSAKeyProvider keyProvider = new RSAKeyProvider() { - @Override - public RSAPublicKey getPublicKeyById(String kid) { - //Received 'kid' value might be null if it wasn't defined in the Token's header - PublicKey publicKey = jwkProvider.get(kid).getPublicKey(); - return (RSAPublicKey) publicKey; - } - - @Override - public RSAPrivateKey getPrivateKey() { - return privateKey; - } - - @Override - public String getPrivateKeyId() { - return privateKeyId; - } -}; - -Algorithm algorithm = Algorithm.RSA256(keyProvider); -//Use the Algorithm to create and verify JWTs. -``` - - -### Time Validation - -The JWT token may include DateNumber fields that can be used to validate that: -* The token was issued in a past date `"iat" < TODAY` -* The token hasn't expired yet `"exp" > TODAY` and -* The token can already be used. `"nbf" < TODAY` - -When verifying a token the time validation occurs automatically, resulting in a `JWTVerificationException` being throw when the values are invalid. If any of the previous fields are missing they won't be considered in this validation. - -To specify a **leeway window** in which the Token should still be considered valid, use the `acceptLeeway()` method in the `JWTVerifier` builder and pass a positive seconds value. This applies to every item listed above. - -```java -JWTVerifier verifier = JWT.require(algorithm) - .acceptLeeway(1) // 1 sec for nbf, iat and exp - .build(); -``` - -You can also specify a custom value for a given Date claim and override the default one for only that claim. - -```java -JWTVerifier verifier = JWT.require(algorithm) - .acceptLeeway(1) //1 sec for nbf and iat - .acceptExpiresAt(5) //5 secs for exp - .build(); -``` - -If you need to test this behavior in your lib/app cast the `Verification` instance to a `BaseVerification` to gain visibility of the `verification.build()` method that accepts a `java.time.Clock`. e.g.: - -```java -BaseVerification verification = (BaseVerification) JWT.require(algorithm) - .acceptLeeway(1) - .acceptExpiresAt(5); -private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC")); -JWTVerifier verifier = verification.build(clock); -``` - -### Header Claims - -#### Algorithm ("alg") - -Returns the Algorithm value or null if it's not defined in the Header. - -```java -String algorithm = jwt.getAlgorithm(); -``` - -#### Type ("typ") - -Returns the Type value or null if it's not defined in the Header. - -```java -String type = jwt.getType(); -``` - -#### Content Type ("cty") - -Returns the Content Type value or null if it's not defined in the Header. - -```java -String contentType = jwt.getContentType(); -``` - -#### Key Id ("kid") - -Returns the Key Id value or null if it's not defined in the Header. - -```java -String keyId = jwt.getKeyId(); -``` - -#### Private Claims - -Additional Claims defined in the token's Header can be obtained by calling `getHeaderClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. - -```java -Claim claim = jwt.getHeaderClaim("owner"); -``` - -When creating a Token with the `JWT.create()` you can specify header Claims by calling `withHeader()` and passing both the map of claims. - -```java -Map headerClaims = new HashMap(); -headerClaims.put("owner", "auth0"); -String token = JWT.create() - .withHeader(headerClaims) - .sign(algorithm); -``` - -> The `alg` and `typ` values will always be included in the Header after the signing process. - - -### Payload Claims - -#### Issuer ("iss") - -Returns the Issuer value or null if it's not defined in the Payload. - -```java -String issuer = jwt.getIssuer(); -``` - -#### Subject ("sub") - -Returns the Subject value or null if it's not defined in the Payload. - -```java -String subject = jwt.getSubject(); -``` - -#### Audience ("aud") - -Returns the Audience value or null if it's not defined in the Payload. - -```java -List audience = jwt.getAudience(); -``` - -#### Expiration Time ("exp") - -Returns the Expiration Time value or null if it's not defined in the Payload. - -```java -Date expiresAt = jwt.getExpiresAt(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant expiresAt = jwt.getExpiresAtAsInstant(); -``` - -#### Not Before ("nbf") - -Returns the Not Before value or null if it's not defined in the Payload. - -```java -Date notBefore = jwt.getNotBefore(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant notBefore = jwt.getNotBeforeAsInstant(); -``` - -#### Issued At ("iat") - -Returns the Issued At value or null if it's not defined in the Payload. - -```java -Date issuedAt = jwt.getIssuedAt(); -``` - -If you prefer to work with `java.time.Instant` instead of `java.util.Date`: - -```java -Instant issuedAt = jwt.getIssuedAtAsInstant(); -``` - -#### JWT ID ("jti") +> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment. +### Installation -Returns the JWT ID value or null if it's not defined in the Payload. +Add the dependency via Maven: -```java -String id = jwt.getId(); +```xml + + com.auth0 + java-jwt + 4.2.1 + ``` -#### Private Claims +or Gradle: -Additional Claims defined in the token's Payload can be obtained by calling `getClaims()` or `getClaim()` and passing the Claim name. A Claim will always be returned, even if it can't be found. You can check if a Claim's value is null by calling `claim.isNull()`. - -```java -Map claims = jwt.getClaims(); //Key is the Claim name -Claim claim = claims.get("isAdmin"); +```gradle +implementation 'com.auth0:jva-jwt:4.2.1' ``` -or +### Create a JWT -```java -Claim claim = jwt.getClaim("isAdmin"); -``` +Use `JWT.create()`, configure the claims, and then call `sign(algorithm)` to sign the JWT. -When creating a Token with the `JWT.create()` you can specify a custom Claim by calling `withClaim()` and passing both the name and the value. +The example below demonstrates this using the `RS256` signing algorithm: ```java -String token = JWT.create() - .withClaim("name", 123) - .withArrayClaim("array", new Integer[]{1, 2, 3}) - .withNullClaim("claim_name") +try { + Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); + String token = JWT.create() + .withIssuer("auth0") .sign(algorithm); +} catch (JWTCreationException exception){ + // Invalid Signing configuration / Couldn't convert Claims. +} ``` -You can also create a JWT by calling `withPayload()` and passing a map of claim names to values: +### Verify a JWT -```java -Map payloadClaims = new HashMap<>(); -payloadClaims.put("@context", "https://auth0.com/"); -String token = JWT.create() - .withPayload(payloadClaims) - .sign(algorithm); -``` +Create a `JWTVerifier` passing the `Algorithm`, and specify any required claim values. -You can also verify custom Claims on the `JWT.require()` by calling `withClaim()` and passing both the name and the required value. +The following example uses `RS256` to verify the JWT. ```java -JWTVerifier verifier = JWT.require(algorithm) - .withClaim("name", 123) - .withArrayClaim("array", 1, 2, 3) - .withNullClaim("null_value") //checks if the claim name provided has null value - .withClaimPresence("claim_presence") //checks if the claim name provided is in the payload - .withClaim("predicate", (claim, decodedJWT) -> "custom_check".equals(claim.asString())) //can be used to run custom verification - .build(); -DecodedJWT jwt = verifier.verify("my.jwt.token"); +String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; +DecodedJWT decodedJWT; +try { + Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); + JWTVerifier verifier = JWT.require(algorithm) + // specify an specific claim validations + .withIssuer("auth0") + // reusable verifier instance + .build(); + + decodedJWT jwt = verifier.verify(token); +} catch (JWTVerificationException exception){ + // Invalid signature/claims +} ``` -> Currently supported classes for custom JWT Claim creation and verification are: Boolean, Integer, Double, String, Date and Arrays of type String and Integer. - - -### Claim Class -The Claim class is a wrapper for the Claim values. It allows you to get the Claim as different class types. The available helpers are: - -#### Primitives -* **asBoolean()**: Returns the Boolean value or null if it can't be converted. -* **asInt()**: Returns the Integer value or null if it can't be converted. -* **asDouble()**: Returns the Double value or null if it can't be converted. -* **asLong()**: Returns the Long value or null if it can't be converted. -* **asString()**: Returns the String value or null if it can't be converted. -* **asInstant()**: Returns the Instant value or null if it can't be converted. -* **asDate()**: Returns the Date value or null if it can't be converted. - -> For `asInstant()` and `asDate()` the value must be a NumericDate (Unix Epoch/Timestamp). Note that the [JWT Standard](https://tools.ietf.org/html/rfc7519#section-2) specified that all the *NumericDate* values must be in seconds. - -#### Custom Classes and Collections -To obtain a Claim as a Collection you'll need to provide the **Class Type** of the contents to convert from. - -* **as(class)**: Returns the value parsed as **Class Type**. For collections you should use the `asArray` and `asList` methods. -* **asMap()**: Returns the value parsed as **Map**. -* **asArray(class)**: Returns the value parsed as an Array of elements of type **Class Type**, or null if the value isn't a JSON Array. -* **asList(class)**: Returns the value parsed as a List of elements of type **Class Type**, or null if the value isn't a JSON Array. - -If the values can't be converted to the given **Class Type** a `JWTDecodeException` will raise. - - +If the token has an invalid signature or the Claim requirement is not met, a `JWTVerificationException` will be thrown. -## What is Auth0? +See the [examples](./EXAMPLES.md) and [JavaDocs](https://javadoc.io/doc/com.auth0/java-jwt/latest) for additional documentation. -Auth0 helps you to: +## API Reference -* Add authentication with [multiple authentication sources](https://auth0.com/docs/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. -* Add authentication through more traditional **[username/password databases](https://auth0.com/docs/connections/database)**. -* Add support for **[linking different user accounts](https://auth0.com/docs/users/user-account-linking)** with the same user. -* Support for generating signed [Json Web Tokens](https://auth0.com/docs/tokens/json-web-tokens) to call your APIs and **flow the user identity** securely. -* Analytics of how, when and where users are logging in. -* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://auth0.com/docs/rules). +- [java-jwt JavaDocs](https://javadoc.io/doc/com.auth0/java-jwt/latest) -## Create a free account in Auth0 +## Feedback -1. Go to [Auth0](https://auth0.com) and click Sign Up. -2. Use Google, GitHub or Microsoft Account to login. +### Contributing -## Issue Reporting +We appreciate feedback and contribution to this repo! Before you get started, please see the following: -If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. +- [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) +- [Auth0's code of conduct guidelines]((https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)) -## Author +### Raise an issue +To provide feedback or report a bug, [please raise an issue on our issue tracker](https://github.com/auth0/java-jwt/issues). -[Auth0](https://auth0.com/) +### Vulnerability Reporting +Please do not report security vulnerabilities on the public Github issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. -## License +--- -This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. +

+ + + + Auth0 Logo + +

+

Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?

+

+This project is licensed under the MIT license. See the LICENSE file for more info.

From 56e663c3b5830e0329160f2165043aff89f8d3bb Mon Sep 17 00:00:00 2001 From: Jeff Allen <103958546+trestletech-dd@users.noreply.github.com> Date: Wed, 26 Oct 2022 16:03:25 -0500 Subject: [PATCH 312/355] Fix the spelling of the dependency name in README (#638) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7786cbaf..8e634ee6 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add the dependency via Maven: or Gradle: ```gradle -implementation 'com.auth0:jva-jwt:4.2.1' +implementation 'com.auth0:java-jwt:4.2.1' ``` ### Create a JWT From 931cd1243c4653744c0f1cc59f182566139cd2c5 Mon Sep 17 00:00:00 2001 From: Wilson Chuks Date: Wed, 23 Nov 2022 07:17:22 +0900 Subject: [PATCH 313/355] Fixed a typographical error on the readme file (#641) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e634ee6..903e37ba 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ try { // reusable verifier instance .build(); - decodedJWT jwt = verifier.verify(token); + decodedJWT = verifier.verify(token); } catch (JWTVerificationException exception){ // Invalid signature/claims } From 14618b5af545e382ce734ef3cb54ba12398460dc Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 11 Jan 2023 14:32:18 +0530 Subject: [PATCH 314/355] Temporarily disable auto release --- .circleci/config.yml | 12 ------------ lib/build.gradle | 11 ----------- 2 files changed, 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c87574b..1675c39f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,5 @@ version: 2.1 orbs: - ship: auth0/ship@0 codecov: codecov/codecov@3 commands: @@ -57,17 +56,6 @@ workflows: build-and-test: jobs: - build - - ship/java-publish: - prefix-tag: false - context: - - publish-gh - - publish-sonatype - filters: - branches: - only: - - master - requires: - - build api-diff: jobs: - api-diff diff --git a/lib/build.gradle b/lib/build.gradle index d6c9b061..6b2fdfe3 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,6 +1,3 @@ -buildscript { - version = "4.2.1" -} plugins { id 'java' @@ -9,13 +6,6 @@ plugins { id 'checkstyle' } -def signingKey = findProperty('signingKey') -def signingKeyPwd = findProperty('signingPassword') - -signing { - useInMemoryPgpKeys(signingKey, signingKeyPwd) -} - checkstyle { toolVersion '10.0' checkstyleTest.enabled = false //We are disabling lint checks for tests @@ -29,7 +19,6 @@ oss { organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.1.0" - skipAssertSigningConfiguration true developers { auth0 { From b4fae643ad27ed5efb28a12da7ac7d52f9917706 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Wed, 11 Jan 2023 17:50:24 +0100 Subject: [PATCH 315/355] Release 4.2.2 (#650) --- CHANGELOG.md | 6 ++++++ README.md | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed3706dc..eaf3feec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [4.2.2](https://github.com/auth0/java-jwt/tree/4.2.2) (2023-01-11) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.1...4.2.2) + +This patch release does not contain any functional changes, but is being released using an updated signing key for verification as part of our commitment to best security practices. +Please review [the README note for additional details.](https://github.com/auth0/java-jwt/blob/master/README.md) + ## [4.2.1](https://github.com/auth0/java-jwt/tree/4.2.1) (2022-10-24) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.0...4.2.1) diff --git a/README.md b/README.md index 903e37ba..8583ed7a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +> **Note** +> As part of our ongoing commitment to best security practices, we have rotated the signing keys used to sign previous releases of this SDK. As a result, new patch builds have been released using the new signing key. Please upgrade at your earliest convenience. +> +> While this change won't affect most developers, if you have implemented a dependency signature validation step in your build process, you may notice a warning that past releases can't be verified. This is expected, and a result of the key rotation process. Updating to the latest version will resolve this for you. + ![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) @@ -45,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.2.1 + 4.2.2 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.2.1' +implementation 'com.auth0:java-jwt:4.2.2' ``` ### Create a JWT From 6e80ea1e5a8624f34f133c3be71078ee9ab83906 Mon Sep 17 00:00:00 2001 From: CodeDead Date: Thu, 26 Jan 2023 22:44:56 +0100 Subject: [PATCH 316/355] Feature/cleanup (#642) * GIT compliance * Simpler HashMap declaration * Replaced usage of deprecated JsonMapper methods, removed unneeded null check * Removed unused import * Removed unused imports Co-authored-by: Jim Anderson --- EXAMPLES.md | 2 +- LICENSE | 2 +- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 11 +++++++---- .../java/com/auth0/jwt/algorithms/RSAAlgorithm.java | 1 - lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java | 2 +- .../test/java/com/auth0/jwt/ConcurrentVerifyTest.java | 3 --- lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java | 2 -- .../com/auth0/jwt/algorithms/HMACAlgorithmTest.java | 1 - settings.gradle | 2 +- 9 files changed, 11 insertions(+), 15 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 8a849303..995e4c1d 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -127,4 +127,4 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs. -``` \ No newline at end of file +``` diff --git a/LICENSE b/LICENSE index 4a7a13ad..bcd1854c 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a99f0fa0..7ed83940 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import java.nio.charset.StandardCharsets; @@ -31,12 +32,14 @@ public final class JWTCreator { private static final SimpleModule module; static { - mapper = new ObjectMapper(); module = new SimpleModule(); module.addSerializer(PayloadClaimsHolder.class, new PayloadSerializer()); module.addSerializer(HeaderClaimsHolder.class, new HeaderSerializer()); - mapper.registerModule(module); - mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + + mapper = JsonMapper.builder() + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .build() + .registerModule(module); } private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) @@ -489,7 +492,7 @@ private static boolean validateClaim(Map map) { return false; } - if (entry.getKey() == null || !(entry.getKey() instanceof String)) { + if (!(entry.getKey() instanceof String)) { return false; } } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 0c7a5b57..ca892e60 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 3746dcd2..8031d8c0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -37,7 +37,7 @@ class BasicHeader implements Header, Serializable { this.type = type; this.contentType = contentType; this.keyId = keyId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap() : tree); + this.tree = Collections.unmodifiableMap(tree == null ? new HashMap<>() : tree); this.objectReader = objectReader; } diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index a06df7b7..32ede1de 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -10,14 +10,11 @@ import org.junit.rules.ExpectedException; import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.util.Collections; import java.util.List; import java.util.concurrent.*; -import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; //@Ignore("Skipping concurrency tests") diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 020e5e37..8fc83b44 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -16,9 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 4a0269cc..9b6ac0c0 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -13,7 +13,6 @@ import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; diff --git a/settings.gradle b/settings.gradle index a4cda2d7..8d5f112c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,4 @@ pluginManagement { } include ':java-jwt' -project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') \ No newline at end of file +project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') From 12ae664a60d5e12d824c0896d5a82ef8522a8b69 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 26 Jan 2023 17:04:32 -0600 Subject: [PATCH 317/355] Fix for `exp` claim considered valid if equal to now (#652) exp claim cannot be equal to now --- .../main/java/com/auth0/jwt/JWTVerifier.java | 6 ++-- lib/src/test/java/com/auth0/jwt/JWTTest.java | 5 ++-- .../java/com/auth0/jwt/JWTVerifierTest.java | 30 +++++++++++++++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 07c86a4c..6cec2026 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -346,7 +346,7 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal), claimVal); } } else { - isValid = assertInstantIsPast(claimVal, leeway, now); + isValid = assertInstantIsLessThanOrEqualToNow(claimVal, leeway, now); if (!isValid) { throw new IncorrectClaimException( String.format("The Token can't be used before %s.", claimVal), claimName, claim); @@ -356,10 +356,10 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew } private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - return !(claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)); + return claimVal == null || now.minus(Duration.ofSeconds(leeway)).isBefore(claimVal); } - private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { + private boolean assertInstantIsLessThanOrEqualToNow(Instant claimVal, long leeway, Instant now) { return !(claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)); } diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index b9f56a2e..087f1e9e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -12,6 +12,7 @@ import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.Base64; @@ -270,12 +271,12 @@ public void shouldGetStringAudience() { @Test public void shouldGetExpirationTime() { long seconds = 1477592L; - Clock clock = Clock.fixed(Instant.ofEpochSecond(seconds), ZoneId.of("UTC")); + Clock mockNow = Clock.fixed(Instant.ofEpochSecond(seconds - 1), ZoneId.of("UTC")); String token = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc1OTJ9.x_ZjkPkKYUV5tdvc0l8go6D_z2kez1MQcOxokXrDc3k"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(clock) + .build(mockNow) .verify(token); assertThat(jwt, is(notNullValue())); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 6d8ba201..5a784b87 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -657,6 +657,7 @@ public void shouldThrowOnNegativeCustomLeeway() { } // Expires At + @Test public void shouldValidateExpiresAtWithLeeway() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; @@ -674,12 +675,26 @@ public void shouldValidateExpiresAtIfPresent() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(mockNow) + .build(mockOneSecondEarlier) .verify(token); assertThat(jwt, is(notNullValue())); } + @Test + public void shouldThrowWhenExpiresAtIsNow() { + // exp must be > now + TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockNow) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z.")); + assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L))); + } + @Test public void shouldThrowOnInvalidExpiresAtIfPresent() { TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { @@ -731,7 +746,18 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { @Test public void shouldValidateNotBeforeIfPresent() { - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTN9.f4zVV0TbbTG5xxDjSoGZ320JIMchGoQCWrnT5MyQdT0"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification + .build(mockOneSecondLater) + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldAcceptNotBeforeEqualToNow() { + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTJ9.71XBtRmkAa4iKnyhbS4NPW-Xr26eAVAdHZgmupS7a5o"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification .build(mockNow) From 902431804ee68a0e4727f41397cd56608918ba37 Mon Sep 17 00:00:00 2001 From: noetro Date: Tue, 31 Jan 2023 03:09:45 +0100 Subject: [PATCH 318/355] Improve JWT parse / decode performance (#620) * Optimise parsing of token for well-defined JWT format * Update error message in test to match new code * Fixing checkstyle issues * Added missing test case for no parts * Return new JWTDecodeException Return a new JWTDecodeException from private utility method `wrongNumberOfParts`, instead of throwing, since we throw from `splitToken()`. * Add JMH support to build script * Add benchmark for decoder and cleanup build file * Optimise JWT deserialisation by re-using threadsafe Jackson objects * Disable lint checks on JMH source set that is for testing * Remove extra line break --------- Co-authored-by: Jim Anderson Co-authored-by: Jim Anderson --- lib/build.gradle | 46 ++++++++++++++++++- .../jwt/benchmark/JWTDecoderBenchmark.java | 20 ++++++++ .../java/com/auth0/jwt/impl/BasicHeader.java | 13 +++--- .../auth0/jwt/impl/HeaderDeserializer.java | 20 +++----- .../java/com/auth0/jwt/impl/JWTParser.java | 22 +++++++-- .../com/auth0/jwt/impl/JsonNodeClaim.java | 36 ++++++++------- .../auth0/jwt/impl/PayloadDeserializer.java | 22 ++++----- .../java/com/auth0/jwt/impl/PayloadImpl.java | 20 ++++---- .../jwt/impl/HeaderDeserializerTest.java | 10 ++-- .../com/auth0/jwt/impl/JWTParserTest.java | 2 +- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 25 ++++++---- .../jwt/impl/PayloadDeserializerTest.java | 19 ++++---- .../com/auth0/jwt/impl/PayloadImplTest.java | 29 ++++++------ 13 files changed, 181 insertions(+), 103 deletions(-) create mode 100644 lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java diff --git a/lib/build.gradle b/lib/build.gradle index 6b2fdfe3..6190a239 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -6,10 +6,28 @@ plugins { id 'checkstyle' } +sourceSets { + jmh { + + } +} + +configurations { + jmhImplementation { + extendsFrom implementation + } +} + checkstyle { toolVersion '10.0' - checkstyleTest.enabled = false //We are disabling lint checks for tests } +//We are disabling lint checks for tests +tasks.named("checkstyleTest").configure({ + enabled = false +}) +tasks.named("checkstyleJmh").configure({ + enabled = false +}) logger.lifecycle("Using version ${version} for ${group}.${name}") @@ -61,6 +79,10 @@ dependencies { testImplementation 'net.jodah:concurrentunit:0.4.6' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.mockito:mockito-core:4.4.0' + + jmhImplementation sourceSets.main.output + jmhImplementation 'org.openjdk.jmh:jmh-core:1.35' + jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.35' } jacoco { @@ -143,3 +165,25 @@ task exportVersion() { new File(rootDir, "version.txt").text = "$version" } } + +// you can pass any arguments JMH accepts via Gradle args. +// Example: ./gradlew runJMH --args="-lrf" +tasks.register('runJMH', JavaExec) { + description 'Run JMH benchmarks.' + group 'verification' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args project.hasProperty("args") ? project.property("args").split() : "" +} +tasks.register('jmhHelp', JavaExec) { + description 'Prints the available command line options for JMH.' + group 'help' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args '-h' +} + diff --git a/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java new file mode 100644 index 00000000..81d3737a --- /dev/null +++ b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java @@ -0,0 +1,20 @@ +package com.auth0.jwt.benchmark; + +import com.auth0.jwt.JWT; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.infra.Blackhole; + +/** + * This class is a JMH benchmark for decoding JWTs. + */ +public class JWTDecoderBenchmark { + private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void throughputDecodeTime(Blackhole blackhole) { + blackhole.consume(JWT.decode(TOKEN)); + } +} diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 8031d8c0..5a881ab5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -2,12 +2,11 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Header; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -23,7 +22,7 @@ class BasicHeader implements Header, Serializable { private final String contentType; private final String keyId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; BasicHeader( String algorithm, @@ -31,14 +30,14 @@ class BasicHeader implements Header, Serializable { String contentType, String keyId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; this.keyId = keyId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap<>() : tree); - this.objectReader = objectReader; + this.tree = tree == null ? Collections.emptyMap() : Collections.unmodifiableMap(tree); + this.objectCodec = objectCodec; } Map getTree() { @@ -67,6 +66,6 @@ public String getKeyId() { @Override public Claim getHeaderClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 9293fd4d..ad6e4ce0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -2,11 +2,11 @@ import com.auth0.jwt.HeaderParams; import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.Header; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; @@ -19,22 +19,14 @@ * * @see JWTParser */ -class HeaderDeserializer extends StdDeserializer { +class HeaderDeserializer extends StdDeserializer
{ - private final ObjectReader objectReader; - - HeaderDeserializer(ObjectReader objectReader) { - this(null, objectReader); - } - - private HeaderDeserializer(Class vc, ObjectReader objectReader) { - super(vc); - - this.objectReader = objectReader; + HeaderDeserializer() { + super(Header.class); } @Override - public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Header deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Map tree = p.getCodec().readValue(p, new TypeReference>() { }); if (tree == null) { @@ -45,7 +37,7 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws String type = getString(tree, HeaderParams.TYPE); String contentType = getString(tree, HeaderParams.CONTENT_TYPE); String keyId = getString(tree, HeaderParams.KEY_ID); - return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); + return new BasicHeader(algorithm, type, contentType, keyId, tree, p.getCodec()); } String getString(Map tree, String claimName) { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index fe1600bd..022520f5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -16,15 +16,21 @@ * {@link HeaderSerializer} and {@link PayloadSerializer}. */ public class JWTParser implements JWTPartsParser { + private static final ObjectMapper DEFAULT_OBJECT_MAPPER = createDefaultObjectMapper(); + private static final ObjectReader DEFAULT_PAYLOAD_READER = DEFAULT_OBJECT_MAPPER.readerFor(Payload.class); + private static final ObjectReader DEFAULT_HEADER_READER = DEFAULT_OBJECT_MAPPER.readerFor(Header.class); + private final ObjectReader payloadReader; private final ObjectReader headerReader; public JWTParser() { - this(getDefaultObjectMapper()); + this.payloadReader = DEFAULT_PAYLOAD_READER; + this.headerReader = DEFAULT_HEADER_READER; } JWTParser(ObjectMapper mapper) { addDeserializers(mapper); + this.payloadReader = mapper.readerFor(Payload.class); this.headerReader = mapper.readerFor(Header.class); } @@ -55,18 +61,24 @@ public Header parseHeader(String json) throws JWTDecodeException { } } - private void addDeserializers(ObjectMapper mapper) { + static void addDeserializers(ObjectMapper mapper) { SimpleModule module = new SimpleModule(); - ObjectReader reader = mapper.reader(); - module.addDeserializer(Payload.class, new PayloadDeserializer(reader)); - module.addDeserializer(Header.class, new HeaderDeserializer(reader)); + module.addDeserializer(Payload.class, new PayloadDeserializer()); + module.addDeserializer(Header.class, new HeaderDeserializer()); mapper.registerModule(module); } static ObjectMapper getDefaultObjectMapper() { + return DEFAULT_OBJECT_MAPPER; + } + + private static ObjectMapper createDefaultObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + addDeserializers(mapper); + return mapper; } 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 456d1515..0a7e22f3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -4,9 +4,9 @@ import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; import java.lang.reflect.Array; @@ -21,12 +21,12 @@ */ class JsonNodeClaim implements Claim { - private final ObjectReader objectReader; + private final ObjectCodec codec; private final JsonNode data; - private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { + private JsonNodeClaim(JsonNode node, ObjectCodec codec) { this.data = node; - this.objectReader = objectReader; + this.codec = codec; } @Override @@ -82,7 +82,7 @@ public T[] asArray(Class clazz) throws JWTDecodeException { T[] arr = (T[]) Array.newInstance(clazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = objectReader.treeToValue(data.get(i), clazz); + arr[i] = codec.treeToValue(data.get(i), clazz); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -99,7 +99,7 @@ public List asList(Class clazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(objectReader.treeToValue(data.get(i), clazz)); + list.add(codec.treeToValue(data.get(i), clazz)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -113,11 +113,11 @@ public Map asMap() throws JWTDecodeException { return null; } - try { - TypeReference> mapType = new TypeReference>() { - }; - JsonParser thisParser = objectReader.treeAsTokens(data); - return thisParser.readValueAs(mapType); + TypeReference> mapType = new TypeReference>() { + }; + + try (JsonParser parser = codec.treeAsTokens(data)) { + return parser.readValueAs(mapType); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to Map", e); } @@ -129,8 +129,8 @@ public T as(Class clazz) throws JWTDecodeException { if (isMissing() || isNull()) { return null; } - return objectReader.treeAsTokens(data).readValueAs(clazz); - } catch (IOException e) { + return codec.treeToValue(data, clazz); + } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); } } @@ -160,21 +160,23 @@ public String toString() { * * @param claimName the Claim to search for. * @param tree the JsonNode tree to search the Claim in. + * @param objectCodec the object codec in use for deserialization * @return a valid non-null Claim. */ - static Claim extractClaim(String claimName, Map tree, ObjectReader objectReader) { + static Claim extractClaim(String claimName, Map tree, ObjectCodec objectCodec) { JsonNode node = tree.get(claimName); - return claimFromNode(node, objectReader); + return claimFromNode(node, objectCodec); } /** * Helper method to create a Claim representation from the given JsonNode. * * @param node the JsonNode to convert into a Claim. + * @param objectCodec the object codec in use for deserialization * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ - static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { - return new JsonNodeClaim(node, objectReader); + static Claim claimFromNode(JsonNode node, ObjectCodec objectCodec) { + return new JsonNodeClaim(node, objectCodec); } } \ No newline at end of file diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 37e70f7a..65fba3ac 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; @@ -24,16 +25,8 @@ */ class PayloadDeserializer extends StdDeserializer { - private final ObjectReader objectReader; - - PayloadDeserializer(ObjectReader reader) { - this(null, reader); - } - - private PayloadDeserializer(Class vc, ObjectReader reader) { - super(vc); - - this.objectReader = reader; + PayloadDeserializer() { + super(Payload.class); } @Override @@ -46,16 +39,17 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE String issuer = getString(tree, RegisteredClaims.ISSUER); String subject = getString(tree, RegisteredClaims.SUBJECT); - List audience = getStringOrArray(tree, RegisteredClaims.AUDIENCE); + List audience = getStringOrArray(p.getCodec(), tree, RegisteredClaims.AUDIENCE); Instant expiresAt = getInstantFromSeconds(tree, RegisteredClaims.EXPIRES_AT); Instant notBefore = getInstantFromSeconds(tree, RegisteredClaims.NOT_BEFORE); Instant issuedAt = getInstantFromSeconds(tree, RegisteredClaims.ISSUED_AT); String jwtId = getString(tree, RegisteredClaims.JWT_ID); - return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); + return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, p.getCodec()); } - List getStringOrArray(Map tree, String claimName) throws JWTDecodeException { + List getStringOrArray(ObjectCodec codec, Map tree, String claimName) + throws JWTDecodeException { JsonNode node = tree.get(claimName); if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) { return null; @@ -67,7 +61,7 @@ List getStringOrArray(Map tree, String claimName) thro List list = new ArrayList<>(node.size()); for (int i = 0; i < node.size(); i++) { try { - list.add(objectReader.treeToValue(node.get(i), String.class)); + list.add(codec.treeToValue(node.get(i), String.class)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to String", e); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index 75e79474..bfd9b0ea 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -2,12 +2,16 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Payload; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.time.Instant; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -30,7 +34,7 @@ class PayloadImpl implements Payload, Serializable { private final Instant issuedAt; private final String jwtId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; PayloadImpl( String issuer, @@ -41,7 +45,7 @@ class PayloadImpl implements Payload, Serializable { Instant issuedAt, String jwtId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.issuer = issuer; this.subject = subject; @@ -50,8 +54,8 @@ class PayloadImpl implements Payload, Serializable { this.notBefore = notBefore; this.issuedAt = issuedAt; this.jwtId = jwtId; - this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); - this.objectReader = objectReader; + this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); + this.objectCodec = objectCodec; } Map getTree() { @@ -111,14 +115,14 @@ public String getId() { @Override public Claim getClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } @Override public Map getClaims() { Map claims = new HashMap<>(tree.size() * 2); for (String name : tree.keySet()) { - claims.put(name, extractClaim(name, tree, objectReader)); + claims.put(name, extractClaim(name, tree, objectCodec)); } return Collections.unmodifiableMap(claims); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java index 328f4ab4..02d782a7 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.TextNode; import org.junit.Before; @@ -22,8 +21,10 @@ import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -34,11 +35,10 @@ public class HeaderDeserializerTest { @Rule public ExpectedException exception = ExpectedException.none(); private HeaderDeserializer deserializer; - private ObjectReader objectReader = new ObjectMapper().reader(); @Before public void setUp() { - deserializer = new HeaderDeserializer(objectReader); + deserializer = new HeaderDeserializer(); } @Test @@ -46,7 +46,7 @@ public void shouldThrowOnNullTree() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("Parsing the Header's JSON resulted on a Null map"); - JsonDeserializer deserializer = new HeaderDeserializer(objectReader); + JsonDeserializer deserializer = new HeaderDeserializer(); JsonParser parser = mock(JsonParser.class); ObjectCodec codec = mock(ObjectCodec.class); DeserializationContext context = mock(DeserializationContext.class); diff --git a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java index a40a23d4..da62131a 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java @@ -42,7 +42,7 @@ public void shouldGetDefaultObjectMapper() { @Test public void shouldAddDeserializers() { ObjectMapper mapper = mock(ObjectMapper.class); - new JWTParser(mapper); + JWTParser.addDeserializers(mapper); verify(mapper).registerModule(any(Module.class)); } 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 18f59f04..a0364953 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; @@ -21,20 +20,31 @@ import java.io.IOException; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; public class JsonNodeClaimTest { private ObjectMapper mapper; - private ObjectReader objectReader; @Rule public ExpectedException exception = ExpectedException.none(); @@ -42,7 +52,6 @@ public class JsonNodeClaimTest { @Before public void setUp() { mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); } @Test @@ -55,7 +64,7 @@ public void shouldGetBooleanValue() { } private Claim claimFromNode(JsonNode value) { - return JsonNodeClaim.claimFromNode(value, objectReader); + return JsonNodeClaim.claimFromNode(value, mapper); } @Test @@ -282,7 +291,7 @@ public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap( JsonNode value = mock(ObjectNode.class); when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT); - ObjectReader mockedMapper = mock(ObjectReader.class); + ObjectMapper mockedMapper = mock(ObjectMapper.class); JsonNodeClaim claim = (JsonNodeClaim) JsonNodeClaim.claimFromNode(value, mockedMapper); JsonNodeClaim spiedClaim = spy(claim); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 2d80bed9..86c4f10f 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -35,9 +35,12 @@ public class PayloadDeserializerTest { public ExpectedException exception = ExpectedException.none(); private PayloadDeserializer deserializer; + private ObjectMapper objectMapper; + @Before public void setUp() { - deserializer = new PayloadDeserializer(new ObjectMapper().reader()); + objectMapper = new ObjectMapper(); + deserializer = new PayloadDeserializer(); } @Test @@ -68,7 +71,7 @@ public void shouldThrowWhenParsingArrayWithObjectValue() throws Exception { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - deserializer.getStringOrArray(tree, "key"); + deserializer.getStringOrArray(objectMapper, tree, "key"); } @Test @@ -123,7 +126,7 @@ public void shouldGetStringArrayWhenParsingArrayNode() { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(2))); assertThat(values, is(IsIterableContaining.hasItems("one", "two"))); @@ -135,7 +138,7 @@ public void shouldGetStringArrayWhenParsingTextNode() { TextNode textNode = new TextNode("something"); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(1))); assertThat(values, is(IsIterableContaining.hasItems("something"))); @@ -147,7 +150,7 @@ public void shouldGetEmptyStringArrayWhenParsingEmptyTextNode() { TextNode textNode = new TextNode(""); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsEmptyCollection.empty())); } @@ -158,7 +161,7 @@ public void shouldGetNullArrayWhenParsingNullNode() { NullNode node = NullNode.getInstance(); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -167,7 +170,7 @@ public void shouldGetNullArrayWhenParsingNullNodeValue() { Map tree = new HashMap<>(); tree.put("key", null); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -177,7 +180,7 @@ public void shouldGetNullArrayWhenParsingNonArrayOrTextNode() { IntNode node = new IntNode(456789); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index da0c880e..5ad7ac68 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -29,29 +29,28 @@ public class PayloadImplTest { private final Instant notBefore = Instant.now(); private final Instant issuedAt = Instant.now(); - private ObjectReader objectReader; + private ObjectMapper objectMapper; @Before public void setUp() { - ObjectMapper mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); + objectMapper = getDefaultObjectMapper(); Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); - payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectReader); + payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectMapper); } @Test public void shouldHaveUnmodifiableTree() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectMapper); payload.getTree().put("something", null); } @Test public void shouldHaveUnmodifiableAudience() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectMapper); payload.getAudience().add("something"); } @@ -63,7 +62,7 @@ public void shouldGetIssuer() { @Test public void shouldGetNullIssuerIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuer(), is(nullValue())); } @@ -76,7 +75,7 @@ public void shouldGetSubject() { @Test public void shouldGetNullSubjectIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getSubject(), is(nullValue())); } @@ -91,7 +90,7 @@ public void shouldGetAudience() { @Test public void shouldGetNullAudienceIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getAudience(), is(nullValue())); } @@ -105,7 +104,7 @@ public void shouldGetExpiresAt() { @Test public void shouldGetNullExpiresAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getExpiresAt(), is(nullValue())); assertThat(payload.getExpiresAtAsInstant(), is(nullValue())); @@ -120,7 +119,7 @@ public void shouldGetNotBefore() { @Test public void shouldGetNullNotBeforeIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getNotBefore(), is(nullValue())); assertThat(payload.getNotBeforeAsInstant(), is(nullValue())); @@ -135,7 +134,7 @@ public void shouldGetIssuedAt() { @Test public void shouldGetNullIssuedAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuedAt(), is(nullValue())); assertThat(payload.getIssuedAtAsInstant(), is(nullValue())); @@ -149,7 +148,7 @@ public void shouldGetJWTId() { @Test public void shouldGetNullJWTIdIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getId(), is(nullValue())); } @@ -163,7 +162,7 @@ public void shouldGetExtraClaim() { @Test public void shouldGetNotNullExtraClaimIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); assertThat(payload.getClaim("missing").isMissing(), is(true)); @@ -175,7 +174,7 @@ public void shouldGetClaims() { Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); tree.put("sub", new TextNode("auth0")); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectMapper); assertThat(payload, is(notNullValue())); Map claims = payload.getClaims(); assertThat(claims, is(notNullValue())); From b610b6635ba1ed948a06fea777eeeecca2364303 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Sun, 12 Feb 2023 21:53:23 -0600 Subject: [PATCH 319/355] Release 4.3.0 (#655) --- CHANGELOG.md | 10 ++++++++++ README.md | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf3feec..61ebad87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [4.3.0](https://github.com/auth0/java-jwt/tree/4.3.0) (2023-02-10) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.2...4.3.0) + +**Changed** +- Improve JWT parse/decode performance [\#620](https://github.com/auth0/java-jwt/pull/620) ([noetro](https://github.com/noetro)) + +**Fixed** +- Fix for exp claim considered valid if equal to now [\#652](https://github.com/auth0/java-jwt/pull/652) ([jimmyjames](https://github.com/jimmyjames)) +- Code cleanup [\#642](https://github.com/auth0/java-jwt/pull/642) ([CodeDead](https://github.com/CodeDead)) + ## [4.2.2](https://github.com/auth0/java-jwt/tree/4.2.2) (2023-01-11) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.1...4.2.2) diff --git a/README.md b/README.md index 8583ed7a..9391f87d 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.2.2 + 4.3.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.2.2' +implementation 'com.auth0:java-jwt:4.3.0' ``` ### Create a JWT From a18955bb99bf6994e424971db3f621bc0cc64a86 Mon Sep 17 00:00:00 2001 From: Andreas Rigas <48296471+andrewrigas@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:27:30 +0100 Subject: [PATCH 320/355] Add support for passing json values for header and payload (#643) --- lib/build.gradle | 2 +- .../main/java/com/auth0/jwt/JWTCreator.java | 48 ++++++++++++ .../java/com/auth0/jwt/JWTCreatorTest.java | 78 ++++++++++++++++++- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 6190a239..d147f2d0 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -72,7 +72,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 7ed83940..f554cbc3 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -98,6 +98,27 @@ public Builder withHeader(Map headerClaims) { return this; } + /** + * Add specific Claims to set as the Header. + * If provided json is null then nothing is changed + * + * @param headerClaimsJson the values to use as Claims in the token's Header. + * @return this same Builder instance. + * @throws IllegalArgumentException if json value has invalid structure + */ + public Builder withHeader(String headerClaimsJson) throws IllegalArgumentException { + if (headerClaimsJson == null) { + return this; + } + + try { + Map headerClaims = mapper.readValue(headerClaimsJson, HashMap.class); + return withHeader(headerClaims); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Invalid header JSON", e); + } + } + /** * Add a specific Key Id ("kid") claim to the Header. * If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, @@ -467,6 +488,33 @@ public Builder withPayload(Map payloadClaims) throws IllegalArgumentE return this; } + /** + * Add specific Claims to set as the Payload. If the provided json is null then + * nothing is changed. + * + *

+ * If any of the claims are invalid, none will be added. + *

+ * + * @param payloadClaimsJson the values to use as Claims in the token's payload. + * @return this same Builder instance. + * @throws IllegalArgumentException if any of the claim keys or null, + * or if the values are not of a supported type, + * or if json value has invalid structure. + */ + public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentException { + if (payloadClaimsJson == null) { + return this; + } + + try { + Map payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class); + return withPayload(payloadClaims); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Invalid payload JSON", e); + } + } + private boolean validatePayload(Map payload) { for (Map.Entry entry : payload.entrySet()) { String key = entry.getKey(); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 8fc83b44..e4ab8cb0 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Rule; import org.junit.Test; @@ -82,13 +83,48 @@ public void shouldAddHeaderClaim() { @Test public void shouldReturnBuilderIfNullMapIsProvided() { + Map nullMap = null; + String nullString = null; String signed = JWTCreator.init() - .withHeader(null) + .withHeader(nullMap) + .withHeader(nullString) .sign(Algorithm.HMAC256("secret")); assertThat(signed, is(notNullValue())); } + @Test + public void shouldSupportJsonValueHeaderWithNestedDataStructure() { + String stringClaim = "someClaim"; + Integer intClaim = 1; + List nestedListClaims = Arrays.asList("1", "2"); + String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}"; + + String jwt = JWTCreator.init() + .withHeader(claimsJson) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + + assertThat(headerJson, JsonMatcher.hasEntry("stringClaim", stringClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("intClaim", intClaim)); + assertThat(headerJson, JsonMatcher.hasEntry("listClaim", nestedListClaims)); + } + + @Test + public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForHeaderClaims() { + String invalidJson = "{ invalidJson }"; + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Invalid header JSON"); + + JWTCreator.init() + .withHeader(invalidJson) + .sign(Algorithm.HMAC256("secret")); + } + @Test public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { Map header = new HashMap<>(); @@ -105,6 +141,7 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() { assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz")); } + @Test public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() { Map header = new HashMap<>(); @@ -715,8 +752,11 @@ public void withPayloadShouldAddBasicClaim() { @Test public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() { + Map nullMap = null; + String nullString = null; String jwt = JWTCreator.init() - .withPayload(null) + .withPayload(nullMap) + .withPayload(nullString) .sign(Algorithm.HMAC256("secret")); assertThat(jwt, is(notNullValue())); @@ -921,10 +961,42 @@ public void withPayloadShouldSupportNullValuesEverywhere() { assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim)); } + @Test + public void withPayloadShouldSupportJsonValueWithNestedDataStructure() { + String stringClaim = "someClaim"; + Integer intClaim = 1; + List nestedListClaims = Arrays.asList("1", "2"); + String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}"; + + String jwt = JWTCreator.init() + .withPayload(claimsJson) + .sign(Algorithm.HMAC256("secret")); + + assertThat(jwt, is(notNullValue())); + String[] parts = jwt.split("\\."); + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + + assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", stringClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", intClaim)); + assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", nestedListClaims)); + } + + @Test + public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForPayloadClaims() { + String invalidJson = "{ invalidJson }"; + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Invalid payload JSON"); + + JWTCreator.init() + .withPayload(invalidJson) + .sign(Algorithm.HMAC256("secret")); + } + @Test public void shouldCreatePayloadWithNullForMap() { String jwt = JWTCreator.init() - .withClaim("name", (Map) null) + .withClaim("name", (Map) null) .sign(Algorithm.HMAC256("secret")); assertThat(jwt, is(notNullValue())); assertTrue(JWT.decode(jwt).getClaim("name").isNull()); From e85a00a00543b2b17d0460e9c1c11c2aa31fbcbc Mon Sep 17 00:00:00 2001 From: Robin Karlsson Date: Mon, 27 Mar 2023 21:57:04 +0200 Subject: [PATCH 321/355] Preserve insertion order for claims (#656) Co-authored-by: Jim Anderson --- .../main/java/com/auth0/jwt/JWTCreator.java | 8 +-- .../java/com/auth0/jwt/JWTCreatorTest.java | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index f554cbc3..0b0d21e4 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -71,8 +71,8 @@ public static class Builder { private final Map headerClaims; Builder() { - this.payloadClaims = new HashMap<>(); - this.headerClaims = new HashMap<>(); + this.payloadClaims = new LinkedHashMap<>(); + this.headerClaims = new LinkedHashMap<>(); } /** @@ -112,7 +112,7 @@ public Builder withHeader(String headerClaimsJson) throws IllegalArgumentExcepti } try { - Map headerClaims = mapper.readValue(headerClaimsJson, HashMap.class); + Map headerClaims = mapper.readValue(headerClaimsJson, LinkedHashMap.class); return withHeader(headerClaims); } catch (JsonProcessingException e) { throw new IllegalArgumentException("Invalid header JSON", e); @@ -508,7 +508,7 @@ public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentExcep } try { - Map payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class); + Map payloadClaims = mapper.readValue(payloadClaimsJson, LinkedHashMap.class); return withPayload(payloadClaims); } catch (JsonProcessingException e) { throw new IllegalArgumentException("Invalid payload JSON", e); diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index e4ab8cb0..53cd267b 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -3,8 +3,8 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -1010,4 +1010,51 @@ public void shouldCreatePayloadWithNullForList() { assertThat(jwt, is(notNullValue())); assertTrue(JWT.decode(jwt).getClaim("name").isNull()); } + + @Test + public void shouldPreserveInsertionOrder() throws Exception { + String taxonomyJson = "{\"class\": \"mammalia\", \"order\": \"carnivora\", \"family\": \"canidae\", \"genus\": \"vulpes\"}"; + List taxonomyClaims = Arrays.asList("class", "order", "family", "genus"); + List headerInsertionOrder = new ArrayList<>(taxonomyClaims); + Map header = new LinkedHashMap<>(); + for (int i = 0; i < 10; i++) { + String key = "h" + i; + header.put(key, "v" + 1); + headerInsertionOrder.add(key); + } + + List payloadInsertionOrder = new ArrayList<>(taxonomyClaims); + JWTCreator.Builder builder = JWTCreator.init() + .withHeader(taxonomyJson) + .withHeader(header) + .withPayload(taxonomyJson); + for (int i = 0; i < 10; i++) { + String name = "c" + i; + builder = builder.withClaim(name, "v" + i); + payloadInsertionOrder.add(name); + } + String signed = builder.sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + String[] parts = signed.split("\\."); + Base64.Decoder urlDecoder = Base64.getUrlDecoder(); + String headerJson = new String(urlDecoder.decode(parts[0]), StandardCharsets.UTF_8); + String payloadJson = new String(urlDecoder.decode(parts[1]), StandardCharsets.UTF_8); + + ObjectMapper objectMapper = new ObjectMapper(); + + List headerFields = new ArrayList<>(); + objectMapper.readValue(headerJson, ObjectNode.class) + .fieldNames().forEachRemaining(headerFields::add); + headerFields.retainAll(headerInsertionOrder); + assertThat("Header insertion order should be preserved", + headerFields, is(equalTo(headerInsertionOrder))); + + List payloadFields = new ArrayList<>(); + objectMapper.readValue(payloadJson, ObjectNode.class) + .fieldNames().forEachRemaining(payloadFields::add); + payloadFields.retainAll(payloadInsertionOrder); + assertThat("Claim insertion order should be preserved", + payloadFields, is(equalTo(payloadInsertionOrder))); + } } From 652bf7dac4c10d1213609a2db6fb50815920ac09 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 30 Mar 2023 22:12:01 -0500 Subject: [PATCH 322/355] update jackson to 2.14.2 (#657) --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index d147f2d0..c4e11764 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -72,7 +72,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 82548a6e7b69ad0c6974519c9ebb313d15237cad Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 31 Mar 2023 13:31:38 -0500 Subject: [PATCH 323/355] Release 4.4.0 (#658) --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ebad87..1b1e75bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [4.4.0](https://github.com/auth0/java-jwt/tree/4.4.0) (2023-03-31) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.3.0...4.4.0) + +**Changed** +- Add support for passing json values for header and payload [\#643](https://github.com/auth0/java-jwt/pull/643) ([andrewrigas](https://github.com/andrewrigas)) +- Preserve insertion order for claims [\#656](https://github.com/auth0/java-jwt/pull/656) ([snago](https://github.com/snago)) +- Update Jackson to 2.14.2 [\#657](https://github.com/auth0/java-jwt/pull/657) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.3.0](https://github.com/auth0/java-jwt/tree/4.3.0) (2023-02-10) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.2...4.3.0) diff --git a/README.md b/README.md index 9391f87d..9f425898 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.3.0 + 4.4.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.3.0' +implementation 'com.auth0:java-jwt:4.4.0' ``` ### Create a JWT From 923e9c46db888c500ff44032fa900b871fae30ed Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 25 Apr 2023 23:02:41 -0300 Subject: [PATCH 324/355] Replace issue templates with issue forms [SDK-4167] (#661) --- .github/ISSUE_TEMPLATE/Bug Report.yml | 67 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature Request.yml | 53 +++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 7 +-- .github/ISSUE_TEMPLATE/feature_request.md | 39 ------------- .github/ISSUE_TEMPLATE/report-a-bug.md | 55 ------------------ 5 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug Report.yml create mode 100644 .github/ISSUE_TEMPLATE/Feature Request.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/report-a-bug.md diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml new file mode 100644 index 00000000..d5d861e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug Report.yml @@ -0,0 +1,67 @@ +name: 🐞 Report a bug +description: Have you found a bug or issue? Create a bug report for this library +labels: ["bug"] + +body: + - type: markdown + attributes: + value: | + **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. + + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have looked into the [Readme](https://github.com/auth0/java-jwt#readme) and [Examples](https://github.com/auth0/java-jwt/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. + required: true + - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) and have not found a suitable solution or answer. + required: true + - label: I have searched the [issues](https://github.com/auth0/java-jwt/issues) and have not found a suitable solution or answer. + required: true + - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. + required: true + - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Provide a clear and concise description of the issue, including what you expected to happen. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. + placeholder: | + 1. Step 1... + 2. Step 2... + 3. ... + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Other libraries that might be involved, or any other relevant information you think would be useful. + validations: + required: false + + - type: input + id: environment-version + attributes: + label: java-jwt version + validations: + required: true + + - type: input + id: environment-java-version + attributes: + label: Java version + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/Feature Request.yml b/.github/ISSUE_TEMPLATE/Feature Request.yml new file mode 100644 index 00000000..38fee433 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature Request.yml @@ -0,0 +1,53 @@ +name: 🧩 Feature request +description: Suggest an idea or a feature for this library +labels: ["feature request"] + +body: + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have looked into the [Readme](https://github.com/auth0/java-jwt#readme) and [Examples](https://github.com/auth0/java-jwt/blob/master/EXAMPLES.md), and have not found a suitable solution or answer. + required: true + - label: I have looked into the [API documentation](https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html) and have not found a suitable solution or answer. + required: true + - label: I have searched the [issues](https://github.com/auth0/java-jwt/issues) and have not found a suitable solution or answer. + required: true + - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. + required: true + - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). + required: true + + - type: textarea + id: description + attributes: + label: Describe the problem you'd like to have solved + description: A clear and concise description of what the problem is. + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: ideal-solution + attributes: + label: Describe the ideal solution + description: A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: alternatives-and-workarounds + attributes: + label: Alternatives and current workarounds + description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3cd7aa53..f58e0249 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - name: Auth0 Community - url: https://community.auth0.com/c/sdks/5 - about: Discuss this SDK in the Auth0 Community forums - - name: Library Documentation - url: https://github.com/auth0/java-jwt/blob/master/README.md - about: Read the library documentation + url: https://community.auth0.com + about: Discuss this library in the Auth0 Community forums diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 68352ba2..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Feature request -about: Suggest an idea or a feature for this project -title: '' -labels: feature request -assignees: '' ---- - - - -### Describe the problem you'd like to have solved - - - -### Describe the ideal solution - - - -## Alternatives and current work-arounds - - - -### Additional information, if any - - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md deleted file mode 100644 index e5cf8c46..00000000 --- a/.github/ISSUE_TEMPLATE/report-a-bug.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Report a bug -about: Have you found a bug or issue? Create a bug report for this SDK -title: '' -labels: bug report -assignees: '' ---- - - - -### Describe the problem - - - -### What was the expected behavior? - - - -### Reproduction - - -- Step 1.. -- Step 2.. -- ... - -### Environment - - - -- **Version of this library used:** -- **Version of Java used:** -- **Other modules/plugins/libraries that might be involved:** -- **Any other relevant information you think would be useful:** From d8fe9a2654a69ff9c39c1fa711b8abb622a4f7d6 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 8 Jun 2023 21:15:57 -0500 Subject: [PATCH 325/355] Empty string audience claim should be deserialized as empty string (#663) --- lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java | 2 +- .../test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 65fba3ac..b1d32a12 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -54,7 +54,7 @@ List getStringOrArray(ObjectCodec codec, Map tree, Str if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) { return null; } - if (node.isTextual() && !node.asText().isEmpty()) { + if (node.isTextual()) { return Collections.singletonList(node.asText()); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 86c4f10f..c3e04013 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -145,14 +145,14 @@ public void shouldGetStringArrayWhenParsingTextNode() { } @Test - public void shouldGetEmptyStringArrayWhenParsingEmptyTextNode() { + public void shouldGetEmptyStringInArrayWhenParsingEmptyTextNode() { Map tree = new HashMap<>(); TextNode textNode = new TextNode(""); tree.put("key", textNode); List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); - assertThat(values, is(IsEmptyCollection.empty())); + assertThat(values, is(IsIterableContaining.hasItem(""))); } @Test From 6ef84cf5a11dfdd45f6f00eb067f27123aa68bf7 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 17 Jul 2023 12:46:01 -0500 Subject: [PATCH 326/355] chore(security): Update and pin Graddle workflow actions --- .github/workflows/gradle-wrapper-validation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index a015578a..20c61e51 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,5 +6,5 @@ jobs: name: "validation/gradlew" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v3 + - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1 From 8c9bbf8d838e6fed8ab589106a47eeb0b381ef28 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 17 Jul 2023 12:46:59 -0500 Subject: [PATCH 327/355] Update gradle-wrapper-validation.yml --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 20c61e51..ce302cb4 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1 + - uses: gradle/wrapper-validation-action@8d49e559aae34d3e0eb16cde532684bc9702762b # pin@v1.0.6 From ef1825b63c81cad6aa5b1e1b7c943ad639bd3140 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 17 Aug 2023 07:09:19 -0500 Subject: [PATCH 328/355] [SDK-4443] Use GitHub Actions for CI (#668) --- .github/workflows/build-and-test.yml | 27 +++++++++++++++++++++++++++ .github/workflows/dependabot.yml | 14 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000..f86ed60e --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,27 @@ +name: auth0/java-jwt/build-and-test + +on: + pull_request: + merge_group: + push: + branches: ["master", "main", "v1"] + +jobs: + gradle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c + with: + arguments: assemble apiDiff check jacocoTestReport --continue --console=plain + - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d + with: + flags: unittests + - uses: actions/upload-artifact@v3 + with: + name: Reports + path: lib/build/reports diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000..f2839f50 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "lib" + schedule: + interval: "daily" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] \ No newline at end of file From 9f7eaa49384ea5f66e985ff97ef2a0520e391570 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 17 Aug 2023 23:16:04 -0500 Subject: [PATCH 329/355] Remove CircleCI (#670) --- .circleci/config.yml | 61 -------------------------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 62 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1675c39f..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 2.1 -orbs: - codecov: codecov/codecov@3 - -commands: - checkout-and-build: - steps: - - checkout - - run: chmod +x gradlew - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: ./gradlew clean build - - save_cache: - paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} - run-tests: - steps: - - run: ./gradlew check jacocoTestReport --continue --console=plain - - codecov/upload - run-api-diff: - steps: - # run apiDiff task - - run: ./gradlew apiDiff - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.txt - - store_artifacts: - path: lib/build/reports/apiDiff/apiDiff.html -jobs: - build: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-tests - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb - api-diff: - docker: - - image: openjdk:11.0-jdk - steps: - - checkout-and-build - - run-api-diff - environment: - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' - _JAVA_OPTIONS: "-Xms512m -Xmx1024m" - TERM: dumb - -workflows: - build-and-test: - jobs: - - build - api-diff: - jobs: - - api-diff diff --git a/README.md b/README.md index 9f425898..4663cbdf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![A Java implementation of JSON Web Token (JWT) - RFC 7519.](https://cdn.auth0.com/website/sdks/banners/java-jwt-banner.png) -[![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) +![Build Status](https://img.shields.io/github/checks-status/auth0/java-jwt/master) [![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt.svg?style=flat-square)](https://codecov.io/github/auth0/java-jwt) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://doge.mit-license.org/) [![Maven Central](https://img.shields.io/maven-central/v/com.auth0/java-jwt.svg?style=flat-square)](https://mvnrepository.com/artifact/com.auth0/java-jwt) From a6fa0b4d338386e7f0650b714444633420af0515 Mon Sep 17 00:00:00 2001 From: Wood Hwang Date: Wed, 13 Sep 2023 09:24:47 +0900 Subject: [PATCH 330/355] Fix typo on a comment in JWTCreator.java (#672) --- lib/src/main/java/com/auth0/jwt/JWTCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index 0b0d21e4..bfcb9147 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -582,7 +582,7 @@ private static boolean isBasicType(Object value) { } /** - * Creates a new JWT and signs is with the given algorithm. + * Creates a new JWT and signs it with the given algorithm. * * @param algorithm used to sign the JWT * @return a new JWT token From bad6035bb87007d6744f4089857e22f8f085e1e5 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Mon, 20 Nov 2023 10:15:43 -0600 Subject: [PATCH 331/355] Remove dead README links (#676) --- lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 27d79909..248af7c5 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -132,7 +132,6 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 256 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -145,7 +144,6 @@ public static Algorithm HMAC256(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 256 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC256 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -158,7 +156,6 @@ public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 384 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -171,7 +168,6 @@ public static Algorithm HMAC384(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 384 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC384 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -184,7 +180,6 @@ public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 512 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ @@ -197,7 +192,6 @@ public static Algorithm HMAC512(String secret) throws IllegalArgumentException { * * @param secret the secret bytes to use in the verify or signing instance. * Ensure the length of the secret is at least 512 bit long - * See HMAC Key Length and Security in README * @return a valid HMAC512 Algorithm. * @throws IllegalArgumentException if the provided Secret is null. */ From d5c05d73a245b5de84830ea67565556232598fb1 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 1 Dec 2023 06:48:30 -0600 Subject: [PATCH 332/355] empty expected audience array should throw InvalidClaimException (#679) --- lib/src/main/java/com/auth0/jwt/JWTVerifier.java | 15 +++++++++++---- .../test/java/com/auth0/jwt/JWTVerifierTest.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 6cec2026..bf180300 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -364,12 +364,19 @@ private boolean assertInstantIsLessThanOrEqualToNow(Instant claimVal, long leewa } private boolean assertValidAudienceClaim( - List audience, - List values, + List actualAudience, + List expectedAudience, boolean shouldContainAll ) { - return !(audience == null || (shouldContainAll && !audience.containsAll(values)) - || (!shouldContainAll && Collections.disjoint(audience, values))); + if (actualAudience == null || expectedAudience == null) { + return false; + } + + if (shouldContainAll) { + return actualAudience.containsAll(expectedAudience); + } else { + return !Collections.disjoint(actualAudience, expectedAudience); + } } private void assertPositive(long leeway) { diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 5a784b87..732d6365 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -310,6 +310,21 @@ public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() { assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null})); } + @Test + public void shouldThrowWhenExpectedEmptyList() { + IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> { + // Token 'aud': 'wide audience' + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ3aWRlIGF1ZGllbmNlIn0.c9anq03XepcuEKWEVsPk9cck0sIIfrT6hHbBsCar49o"; + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withAnyOfAudience(new String[0]) + .build() + .verify(token); + }); + assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience.")); + assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE)); + assertThat(e.getClaimValue().asString(), is("wide audience")); + } + @Test public void shouldNotReplaceWhenMultipleChecksAreAdded() { JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) From d97f4e6df09d5437aede60776fa69e0ad49824af Mon Sep 17 00:00:00 2001 From: Kasper Karlsson Date: Tue, 6 Feb 2024 05:27:38 +0100 Subject: [PATCH 333/355] Fix typo in example code (#682) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4663cbdf..285f56ec 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ DecodedJWT decodedJWT; try { Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey); JWTVerifier verifier = JWT.require(algorithm) - // specify an specific claim validations + // specify any specific claim validations .withIssuer("auth0") // reusable verifier instance .build(); From 3aa997afc11d109a8565d3101711bfb0ce110bf6 Mon Sep 17 00:00:00 2001 From: Steven Wong Date: Thu, 25 Jul 2024 22:25:13 +0800 Subject: [PATCH 334/355] Update codeowner file with new GitHub team name --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 60f116c0..7958e8bd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @auth0/dx-sdks-engineer +* @auth0/project-dx-sdks-engineer-codeowner From 0c76c94784e1e00010e9848b09eff2c7df1ae4d6 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 19 Dec 2023 15:07:35 +0100 Subject: [PATCH 335/355] Automate release workflow --- .github/actions/get-prerelease/action.yml | 30 +++++++ .github/actions/get-release-notes/action.yml | 42 ++++++++++ .github/actions/get-version/action.yml | 21 +++++ .github/actions/maven-publish/action.yml | 44 ++++++++++ .github/actions/release-create/action.yml | 47 +++++++++++ .github/actions/tag-exists/action.yml | 36 ++++++++ .github/workflows/java-release.yml | 88 ++++++++++++++++++++ .github/workflows/release.yml | 27 ++++++ lib/build.gradle | 16 ++-- 9 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 .github/actions/get-prerelease/action.yml create mode 100644 .github/actions/get-release-notes/action.yml create mode 100644 .github/actions/get-version/action.yml create mode 100644 .github/actions/maven-publish/action.yml create mode 100644 .github/actions/release-create/action.yml create mode 100644 .github/actions/tag-exists/action.yml create mode 100644 .github/workflows/java-release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/actions/get-prerelease/action.yml b/.github/actions/get-prerelease/action.yml new file mode 100644 index 00000000..ce7acdc3 --- /dev/null +++ b/.github/actions/get-prerelease/action.yml @@ -0,0 +1,30 @@ +name: Return a boolean indicating if the version contains prerelease identifiers + +# +# Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + version: + required: true + +outputs: + prerelease: + value: ${{ steps.get_prerelease.outputs.PRERELEASE }} + +runs: + using: composite + + steps: + - id: get_prerelease + shell: bash + run: | + if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then + echo "PRERELEASE=true" >> $GITHUB_OUTPUT + else + echo "PRERELEASE=false" >> $GITHUB_OUTPUT + fi + env: + VERSION: ${{ inputs.version }} diff --git a/.github/actions/get-release-notes/action.yml b/.github/actions/get-release-notes/action.yml new file mode 100644 index 00000000..287d2066 --- /dev/null +++ b/.github/actions/get-release-notes/action.yml @@ -0,0 +1,42 @@ +name: Return the release notes extracted from the body of the PR associated with the release. + +# +# Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. +# +# TODO: Remove once the common repo is public. +# +inputs: + version: + required: true + repo_name: + required: false + repo_owner: + required: true + token: + required: true + +outputs: + release-notes: + value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} + +runs: + using: composite + + steps: + - uses: actions/github-script@v7 + id: get_release_notes + with: + result-encoding: string + script: | + const { data: pulls } = await github.rest.pulls.list({ + owner: process.env.REPO_OWNER, + repo: process.env.REPO_NAME, + state: 'all', + head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, + }); + core.setOutput('RELEASE_NOTES', pulls[0].body); + env: + GITHUB_TOKEN: ${{ inputs.token }} + REPO_OWNER: ${{ inputs.repo_owner }} + REPO_NAME: ${{ inputs.repo_name }} + VERSION: ${{ inputs.version }} diff --git a/.github/actions/get-version/action.yml b/.github/actions/get-version/action.yml new file mode 100644 index 00000000..9440ec92 --- /dev/null +++ b/.github/actions/get-version/action.yml @@ -0,0 +1,21 @@ +name: Return the version extracted from the branch name + +# +# Returns the version from the .version file. +# +# TODO: Remove once the common repo is public. +# + +outputs: + version: + value: ${{ steps.get_version.outputs.VERSION }} + +runs: + using: composite + + steps: + - id: get_version + shell: bash + run: | + VERSION=$(head -1 .version) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml new file mode 100644 index 00000000..ee477061 --- /dev/null +++ b/.github/actions/maven-publish/action.yml @@ -0,0 +1,44 @@ +name: Publish release to Java + +inputs: + ossr-username: + required: true + ossr-password: + required: true + signing-key: + required: true + signing-password: + required: true + java-version: + required: true + is-android: + required: true + version: + required: true + +runs: + using: composite + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + shell: bash + run: | + curl -s "https://get.sdkman.io" | bash + source "/home/runner/.sdkman/bin/sdkman-init.sh" + sdk list java + sdk install java ${{ inputs.java-version }} && sdk default java ${{ inputs.java-version }} + + - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 + + - name: Publish Java + shell: bash + if: inputs.is-android == 'false' + run: ./gradlew clean assemble sign publishMavenJavaPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" + + - name: Publish Android + shell: bash + if: inputs.is-android == 'true' + run: ./gradlew clean assemble sign publishAndroidLibraryPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" diff --git a/.github/actions/release-create/action.yml b/.github/actions/release-create/action.yml new file mode 100644 index 00000000..6a2bf804 --- /dev/null +++ b/.github/actions/release-create/action.yml @@ -0,0 +1,47 @@ +name: Create a GitHub release + +# +# Creates a GitHub release with the given version. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + files: + required: false + name: + required: true + body: + required: true + tag: + required: true + commit: + required: true + draft: + default: false + required: false + prerelease: + default: false + required: false + fail_on_unmatched_files: + default: true + required: false + +runs: + using: composite + + steps: + - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + body: ${{ inputs.body }} + name: ${{ inputs.name }} + tag_name: ${{ inputs.tag }} + target_commitish: ${{ inputs.commit }} + draft: ${{ inputs.draft }} + prerelease: ${{ inputs.prerelease }} + fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} + files: ${{ inputs.files }} + env: + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/actions/tag-exists/action.yml b/.github/actions/tag-exists/action.yml new file mode 100644 index 00000000..b5fbdb73 --- /dev/null +++ b/.github/actions/tag-exists/action.yml @@ -0,0 +1,36 @@ +name: Return a boolean indicating if a tag already exists for the repository + +# +# Returns a simple true/false boolean indicating whether the tag exists or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + tag: + required: true + +outputs: + exists: + description: 'Whether the tag exists or not' + value: ${{ steps.tag-exists.outputs.EXISTS }} + +runs: + using: composite + + steps: + - id: tag-exists + shell: bash + run: | + GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" + http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") + if [ "$http_status_code" -ne "404" ] ; then + echo "EXISTS=true" >> $GITHUB_OUTPUT + else + echo "EXISTS=false" >> $GITHUB_OUTPUT + fi + env: + TAG_NAME: ${{ inputs.tag }} + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml new file mode 100644 index 00000000..3f81eb14 --- /dev/null +++ b/.github/workflows/java-release.yml @@ -0,0 +1,88 @@ +name: Create Java and GitHub Release + +on: + workflow_call: + inputs: + java-version: + required: true + type: string + is-android: + required: true + type: string + secrets: + ossr-username: + required: true + ossr-password: + required: true + signing-key: + required: true + signing-password: + required: true + github-token: + required: true + +### TODO: Replace instances of './.github/actions/' w/ `auth0/dx-sdk-actions/` and append `@latest` after the common `dx-sdk-actions` repo is made public. +### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public. + +jobs: + release: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + environment: release + + steps: + # Checkout the code + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Get the version from the branch name + - id: get_version + uses: ./.github/actions/get-version + + # Get the prerelease flag from the branch name + - id: get_prerelease + uses: ./.github/actions/get-prerelease + with: + version: ${{ steps.get_version.outputs.version }} + + # Get the release notes + - id: get_release_notes + uses: ./.github/actions/get-release-notes + with: + token: ${{ secrets.github-token }} + version: ${{ steps.get_version.outputs.version }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} + + # Check if the tag already exists + - id: tag_exists + uses: ./.github/actions/tag-exists + with: + tag: ${{ steps.get_version.outputs.version }} + token: ${{ secrets.github-token }} + + # If the tag already exists, exit with an error + - if: steps.tag_exists.outputs.exists == 'true' + run: exit 1 + + # Publish the release to Maven + - uses: ./.github/actions/maven-publish + with: + java-version: ${{ inputs.java-version }} + is-android: ${{ inputs.is-android }} + version: ${{ steps.get_version.outputs.version }} + ossr-username: ${{ secrets.ossr-username }} + ossr-password: ${{ secrets.ossr-password }} + signing-key: ${{ secrets.signing-key }} + signing-password: ${{ secrets.signing-password }} + + # Create a release for the tag + - uses: ./.github/actions/release-create + with: + token: ${{ secrets.github-token }} + name: ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_release_notes.outputs.release-notes }} + tag: ${{ steps.get_version.outputs.version }} + commit: ${{ github.sha }} + prerelease: ${{ steps.get_prerelease.outputs.prerelease }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..63482cca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Create GitHub Release + +on: + pull_request: + types: + - closed + workflow_dispatch: + +permissions: + contents: write + +### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. +### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `maven-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. +### TODO: Also remove `java-release` workflow from this repo's .github/workflows folder once the repo is public. + +jobs: + release: + uses: ./.github/workflows/java-release.yml + with: + java-version: 8.0.382-tem + is-android: false + secrets: + ossr-username: ${{ secrets.OSSR_USERNAME }} + ossr-password: ${{ secrets.OSSR_PASSWORD }} + signing-key: ${{ secrets.SIGNING_KEY }} + signing-password: ${{ secrets.SIGNING_PASSWORD }} + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/lib/build.gradle b/lib/build.gradle index c4e11764..aa134c32 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -31,12 +31,16 @@ tasks.named("checkstyleJmh").configure({ logger.lifecycle("Using version ${version} for ${group}.${name}") +def signingKey = findProperty('signingKey') +def signingKeyPwd = findProperty('signingPassword') + oss { name "java jwt" repository "java-jwt" organization "auth0" description "Java implementation of JSON Web Token (JWT)" baselineCompareVersion "4.1.0" + skipAssertSigningConfiguration true developers { auth0 { @@ -54,6 +58,10 @@ oss { } } +signing { + useInMemoryPgpKeys(signingKey, signingKeyPwd) +} + java { toolchain { languageVersion = JavaLanguageVersion.of(11) @@ -158,14 +166,6 @@ jar { compileModuleInfoJava.dependsOn compileJava classes.dependsOn compileModuleInfoJava -// Creates a version.txt file containing the current version of the SDK. -// This file is picked up and parsed by our Ship Orb to determine the version. -task exportVersion() { - doLast { - new File(rootDir, "version.txt").text = "$version" - } -} - // you can pass any arguments JMH accepts via Gradle args. // Example: ./gradlew runJMH --args="-lrf" tasks.register('runJMH', JavaExec) { From 05fef18c2b3cdfd5ece2cb51ebcbff5a6ebce127 Mon Sep 17 00:00:00 2001 From: Poovamraj T T Date: Tue, 19 Dec 2023 15:10:35 +0100 Subject: [PATCH 336/355] Add .version and modify .shiprc --- .shiprc | 1 + .version | 1 + 2 files changed, 2 insertions(+) create mode 100644 .version diff --git a/.shiprc b/.shiprc index fe59345e..1b83cc62 100644 --- a/.shiprc +++ b/.shiprc @@ -1,6 +1,7 @@ { "files": { "README.md": [], + ".version": [], "lib/build.gradle": ["version = \"{MAJOR}.{MINOR}.{PATCH}\""] }, "prefixVersion": false diff --git a/.version b/.version new file mode 100644 index 00000000..64b5ae39 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +4.4.0 \ No newline at end of file From 9cd2b043280e3f66778d75198aa34bf821696c3c Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Wed, 20 Dec 2023 14:26:46 +0100 Subject: [PATCH 337/355] Update .github/workflows/release.yml Co-authored-by: Jim Anderson --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63482cca..7a98f05e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: release: uses: ./.github/workflows/java-release.yml with: - java-version: 8.0.382-tem + java-version: 11.0.21-tem is-android: false secrets: ossr-username: ${{ secrets.OSSR_USERNAME }} From 876d7456d362e031fcb1a56609e2c850bede426f Mon Sep 17 00:00:00 2001 From: Tanya Sinha Date: Thu, 14 Nov 2024 16:05:59 +0530 Subject: [PATCH 338/355] Add reversing lab scanner (#695) --- .github/actions/rl-scanner/action.yml | 66 ++++++++++++++++++++++++ .github/workflows/release.yml | 14 +++++ .github/workflows/rl-secure.yml | 73 +++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 .github/actions/rl-scanner/action.yml create mode 100644 .github/workflows/rl-secure.yml diff --git a/.github/actions/rl-scanner/action.yml b/.github/actions/rl-scanner/action.yml new file mode 100644 index 00000000..fbf81217 --- /dev/null +++ b/.github/actions/rl-scanner/action.yml @@ -0,0 +1,66 @@ +name: 'Reversing Labs Scanner' +description: 'Runs the Reversing Labs scanner on a specified artifact.' +inputs: + artifact-path: + description: 'Path to the artifact to be scanned.' + required: true + version: + description: 'Version of the artifact.' + required: true + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python dependencies + shell: bash + run: | + pip install boto3 requests + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }} + aws-region: us-east-1 + mask-aws-account-id: true + + - name: Install RL Wrapper + shell: bash + run: | + pip install rl-wrapper>=1.0.0 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" + - name: Run RL Scanner + shell: bash + env: + RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }} + PYTHONUNBUFFERED: 1 + run: | + if [ ! -f "${{ inputs.artifact-path }}" ]; then + echo "Artifact not found: ${{ inputs.artifact-path }}" + exit 1 + fi + rl-wrapper \ + --artifact "${{ inputs.artifact-path }}" \ + --name "${{ github.event.repository.name }}" \ + --version "${{ inputs.version }}" \ + --repository "${{ github.repository }}" \ + --commit "${{ github.sha }}" \ + --build-env "github_actions" \ + --suppress_output + # Check the outcome of the scanner + if [ $? -ne 0 ]; then + echo "RL Scanner failed." + echo "scan-status=failed" >> $GITHUB_ENV + exit 1 + else + echo "RL Scanner passed." + echo "scan-status=success" >> $GITHUB_ENV + fi +outputs: + scan-status: + description: 'The outcome of the scan process.' + value: ${{ env.scan-status }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a98f05e..49e48059 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,14 +8,28 @@ on: permissions: contents: write + id-token: write # This is required for requesting the JWT ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `maven-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. ### TODO: Also remove `java-release` workflow from this repo's .github/workflows folder once the repo is public. jobs: + rl-scanner: + uses: ./.github/workflows/rl-secure.yml + with: + java-version: 11 + artifact-name: 'java-jwt.tgz' + secrets: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} release: uses: ./.github/workflows/java-release.yml + needs: rl-scanner with: java-version: 11.0.21-tem is-android: false diff --git a/.github/workflows/rl-secure.yml b/.github/workflows/rl-secure.yml new file mode 100644 index 00000000..ef329594 --- /dev/null +++ b/.github/workflows/rl-secure.yml @@ -0,0 +1,73 @@ +name: RL-Secure Workflow + +on: + workflow_call: + inputs: + java-version: + required: true + type: string + artifact-name: + required: true + type: string + secrets: + RLSECURE_LICENSE: + required: true + RLSECURE_SITE_KEY: + required: true + SIGNAL_HANDLER_TOKEN: + required: true + PRODSEC_TOOLS_USER: + required: true + PRODSEC_TOOLS_TOKEN: + required: true + PRODSEC_TOOLS_ARN: + required: true + +jobs: + checkout-build-scan-only: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + outputs: + scan-status: ${{ steps.rl-scan-conclusion.outcome }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ inputs.java-version }} + + - name: Build with Gradle + uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c + with: + arguments: assemble apiDiff check jacocoTestReport --continue --console=plain + + - name: Get Artifact Version + id: get_version + uses: ./.github/actions/get-version + + - name: Create tgz build artifact + run: | + tar -czvf ${{ inputs.artifact-name }} * + + - name: Run RL Scanner + id: rl-scan-conclusion + uses: ./.github/actions/rl-scanner + with: + artifact-path: "$(pwd)/${{ inputs.artifact-name }}" + version: "${{ steps.get_version.outputs.version }}" + env: + RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} + RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} + SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} + PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }} + PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }} + PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }} + + - name: Output scan result + run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENV \ No newline at end of file From 41d93afce36dd07431f45f39401f24840835f54e Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 15 Nov 2024 21:12:00 +0530 Subject: [PATCH 339/355] added snyk workflow --- .github/workflows/snyk.yml | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/snyk.yml diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml new file mode 100644 index 00000000..9394832d --- /dev/null +++ b/.github/workflows/snyk.yml @@ -0,0 +1,47 @@ +name: Snyk + +on: + merge_group: + workflow_dispatch: + pull_request_target: + types: + - opened + - synchronize + push: + branches: + - master + schedule: + - cron: '30 0 1,15 * *' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + authorize: + name: Authorize + environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} + runs-on: ubuntu-latest + steps: + - run: true + + check: + needs: authorize + + name: Check for Vulnerabilities + runs-on: ubuntu-latest + + steps: + - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' + run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. + + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + + - uses: snyk/actions/gradle-jdk11@b98d498629f1c368650224d6d212bf7dfa89e4bf # pin@0.4.0 + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} \ No newline at end of file From b915cb6ebe089ac68ae70221792576d322c9a924 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 15 Nov 2024 21:16:11 +0530 Subject: [PATCH 340/355] removed pull_request_target --- .github/workflows/snyk.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 9394832d..457b6afa 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -3,7 +3,7 @@ name: Snyk on: merge_group: workflow_dispatch: - pull_request_target: + pull_request: types: - opened - synchronize @@ -21,16 +21,8 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - authorize: - name: Authorize - environment: ${{ github.actor != 'dependabot[bot]' && github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} - runs-on: ubuntu-latest - steps: - - run: true check: - needs: authorize - name: Check for Vulnerabilities runs-on: ubuntu-latest From de23d8f21f49e4d7a5b5572ac1416d13f2ddc5e8 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jan 2025 18:21:41 +0530 Subject: [PATCH 341/355] add java 21 in CI --- .github/workflows/build-and-test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f86ed60e..32886d68 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,12 +9,17 @@ on: jobs: gradle: runs-on: ubuntu-latest + + strategy: + matrix: + java-version: [11, 17, 21] + steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: ${{ matrix.java-version }} - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c with: arguments: assemble apiDiff check jacocoTestReport --continue --console=plain From 9e04f118db4f3356e673aa981be95a56f8ddf84d Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jan 2025 18:25:53 +0530 Subject: [PATCH 342/355] added java 21 --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 32886d68..5ca24522 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - java-version: [11, 17, 21] + java-version: [11, 21] steps: - uses: actions/checkout@v3 From cc705395f5099d09a8a33a0ea35c87915e38aa66 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 17 Jan 2025 10:27:38 +0530 Subject: [PATCH 343/355] added java 21 tests --- .github/workflows/build-and-test.yml | 7 +------ lib/build.gradle | 11 +++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5ca24522..f86ed60e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,17 +9,12 @@ on: jobs: gradle: runs-on: ubuntu-latest - - strategy: - matrix: - java-version: [11, 21] - steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: temurin - java-version: ${{ matrix.java-version }} + java-version: 11 - uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c with: arguments: assemble apiDiff check jacocoTestReport --continue --console=plain diff --git a/lib/build.gradle b/lib/build.gradle index aa134c32..77de85a7 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -154,9 +154,20 @@ def testJava17 = tasks.register('testJava17', Test) { shouldRunAfter(tasks.named('test')) } +def testJava21 = tasks.register('testJava21', Test) { + description = 'Runs unit tests on Java 21.' + group = 'verification' + + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + }) + shouldRunAfter(tasks.named('test')) +} + tasks.named('check') { dependsOn(testJava8) dependsOn(testJava17) + dependsOn(testJava21) } jar { From d20aec6d58cd37ab518b987cd811bb6480a2ff97 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 17 Jan 2025 10:35:41 +0530 Subject: [PATCH 344/355] updated jacoco toll version --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index 77de85a7..bff7bb9c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -94,7 +94,7 @@ dependencies { } jacoco { - toolVersion = "0.8.7" + toolVersion = "0.8.10" } jacocoTestReport { From 8278f62be812496210c5fa5a11192741d2d6c8c7 Mon Sep 17 00:00:00 2001 From: Carlos Galan Cladera Date: Wed, 11 Dec 2024 14:53:41 +0100 Subject: [PATCH 345/355] fix: upgrade jackson-core to 2.15 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index aa134c32..c2c77533 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -80,7 +80,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From 3840a1eaf07e2de55c7676b90d6b398526355ff1 Mon Sep 17 00:00:00 2001 From: Carlos Galan Cladera Date: Wed, 11 Dec 2024 15:11:01 +0100 Subject: [PATCH 346/355] fix: upgrade jackson-core to 2.15.4 --- lib/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle b/lib/build.gradle index c2c77533..16a55af1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -80,7 +80,7 @@ javadoc { } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.4' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'junit:junit:4.13.2' From a52bec0224ea087116c807d8f895fa98d1596013 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 14:02:26 +0530 Subject: [PATCH 347/355] added maven-publish changes --- .github/actions/maven-publish/action.yml | 25 ++--- .github/workflows/java-release.yml | 9 +- .github/workflows/release.yml | 3 +- gradle.properties | 22 +++++ gradle/maven-publish.gradle | 113 +++++++++++++++++++++++ gradle/versioning.gradle | 17 ++++ lib/build.gradle | 89 +++++++++++++----- settings.gradle | 3 - 8 files changed, 231 insertions(+), 50 deletions(-) create mode 100644 gradle/maven-publish.gradle create mode 100644 gradle/versioning.gradle diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index ee477061..0d280cbe 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -1,20 +1,16 @@ name: Publish release to Java inputs: + java-version: + required: true ossr-username: required: true - ossr-password: + ossr-token: required: true signing-key: required: true signing-password: required: true - java-version: - required: true - is-android: - required: true - version: - required: true runs: using: composite @@ -33,12 +29,11 @@ runs: - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 - - name: Publish Java - shell: bash - if: inputs.is-android == 'false' - run: ./gradlew clean assemble sign publishMavenJavaPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" - - - name: Publish Android + - name: Publish Android/Java Packages to Maven shell: bash - if: inputs.is-android == 'true' - run: ./gradlew clean assemble sign publishAndroidLibraryPublicationToMavenRepository -PisSnapshot=false -Pversion="${{ inputs.version }}" -PossrhUsername="${{ inputs.ossr-username }}" -PossrhPassword="${{ inputs.ossr-password }}" -PsigningKey="${{ inputs.signing-key }}" -PsigningPassword="${{ inputs.signing-password }}" + run: ./gradlew publish -PisSnapshot=false --stacktrace + env: + MAVEN_USERNAME: ${{ inputs.ossr-username }} + MAVEN_PASSWORD: ${{ inputs.ossr-token }} + SIGNING_KEY: ${{ inputs.signing-key}} + SIGNING_PASSWORD: ${{ inputs.signing-password}} \ No newline at end of file diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml index 3f81eb14..ddce3e59 100644 --- a/.github/workflows/java-release.yml +++ b/.github/workflows/java-release.yml @@ -6,13 +6,10 @@ on: java-version: required: true type: string - is-android: - required: true - type: string secrets: ossr-username: required: true - ossr-password: + ossr-token: required: true signing-key: required: true @@ -70,10 +67,8 @@ jobs: - uses: ./.github/actions/maven-publish with: java-version: ${{ inputs.java-version }} - is-android: ${{ inputs.is-android }} - version: ${{ steps.get_version.outputs.version }} ossr-username: ${{ secrets.ossr-username }} - ossr-password: ${{ secrets.ossr-password }} + ossr-token: ${{ secrets.ossr-token }} signing-key: ${{ secrets.signing-key }} signing-password: ${{ secrets.signing-password }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49e48059..2b00e426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,10 +32,9 @@ jobs: needs: rl-scanner with: java-version: 11.0.21-tem - is-android: false secrets: ossr-username: ${{ secrets.OSSR_USERNAME }} - ossr-password: ${{ secrets.OSSR_PASSWORD }} + ossr-token: ${{ secrets.OSSR_TOKEN }} signing-key: ${{ secrets.SIGNING_KEY }} signing-password: ${{ secrets.SIGNING_PASSWORD }} github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/gradle.properties b/gradle.properties index aac7c9b4..b4d8583f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,25 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +GROUP=com.auth0 +POM_ARTIFACT_ID=java-jwt +VERSION_NAME=4.4.0 + +POM_NAME=java jwt +POM_DESCRIPTION=Java client library for the Auth0 platform +POM_PACKAGING=jar + +POM_URL=https://github.com/auth0/java-jwt +POM_SCM_URL=https://github.com/auth0/java-jwt + +POM_SCM_CONNECTION=scm:git:https://github.com/auth0/java-jwt.git +POM_SCM_DEV_CONNECTION=scm:git:https://github.com/auth0/java-jwt.git + +POM_LICENCE_NAME=The MIT License (MIT) +POM_LICENCE_URL=https://raw.githubusercontent.com/auth0/java-jwt/master/LICENSE +POM_LICENCE_DIST=repo + +POM_DEVELOPER_ID=auth0 +POM_DEVELOPER_NAME=Auth0 +POM_DEVELOPER_EMAIL=oss@auth0.com \ No newline at end of file diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle new file mode 100644 index 00000000..206a581b --- /dev/null +++ b/gradle/maven-publish.gradle @@ -0,0 +1,113 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +task('sourcesJar', type: Jar, dependsOn: classes) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +task('javadocJar', type: Jar, dependsOn: javadoc) { + archiveClassifier = 'javadoc' + from javadoc.getDestinationDir() +} +tasks.withType(Javadoc).configureEach { + javadocTool = javaToolchains.javadocToolFor { + // Use latest JDK for javadoc generation + languageVersion = JavaLanguageVersion.of(17) + } +} + +javadoc { + // Specify the Java version that the project will use + options.addStringOption('-release', "8") +} +artifacts { + archives sourcesJar, javadocJar +} + + +final releaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +final snapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + +publishing { + publications { + mavenJava(MavenPublication) { + + groupId = GROUP + artifactId = POM_ARTIFACT_ID + version = getVersionName() + + artifact("$buildDir/libs/${project.name}-${version}.jar") + artifact sourcesJar + artifact javadocJar + + pom { + name = POM_NAME + packaging = POM_PACKAGING + description = POM_DESCRIPTION + url = POM_URL + + licenses { + license { + name = POM_LICENCE_NAME + url = POM_LICENCE_URL + distribution = POM_LICENCE_DIST + } + } + + developers { + developer { + id = POM_DEVELOPER_ID + name = POM_DEVELOPER_NAME + email = POM_DEVELOPER_EMAIL + } + } + + scm { + url = POM_SCM_URL + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEV_CONNECTION + } + + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + project.configurations.implementation.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } + repositories { + maven { + name = "sonatype" + url = version.endsWith('SNAPSHOT') ? snapshotRepositoryUrl : releaseRepositoryUrl + credentials { + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") + } + } + } +} + +signing { + def signingKey = System.getenv("SIGNING_KEY") + def signingPassword = System.getenv("SIGNING_PASSWORD") + useInMemoryPgpKeys(signingKey, signingPassword) + + sign publishing.publications.mavenJava +} + +javadoc { + if(JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption('html5', true) + } +} + +tasks.named('publish').configure { + dependsOn tasks.named('assemble') +} \ No newline at end of file diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle new file mode 100644 index 00000000..3441ae11 --- /dev/null +++ b/gradle/versioning.gradle @@ -0,0 +1,17 @@ +def getVersionFromFile() { + def versionFile = rootProject.file('.version') + return versionFile.text.readLines().first().trim() +} + +def isSnapshot() { + return hasProperty('isSnapshot') ? isSnapshot.toBoolean() : true +} + +def getVersionName() { + return isSnapshot() ? project.version+"-SNAPSHOT" : project.version +} + +ext { + getVersionName = this.&getVersionName + getVersionFromFile = this.&getVersionFromFile +} \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index 148bce72..167f234a 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,9 +1,19 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + // https://github.com/melix/japicmp-gradle-plugin/issues/36 + classpath 'com.google.guava:guava:31.1-jre' + } +} plugins { id 'java' id 'jacoco' - id 'com.auth0.gradle.oss-library.java' id 'checkstyle' + id 'me.champeau.gradle.japicmp' version '0.2.9' } sourceSets { @@ -29,37 +39,69 @@ tasks.named("checkstyleJmh").configure({ enabled = false }) -logger.lifecycle("Using version ${version} for ${group}.${name}") +apply from: rootProject.file('gradle/versioning.gradle') + +version = getVersionFromFile() +group = GROUP +logger.lifecycle("Using version ${version} for ${name} group $group") -def signingKey = findProperty('signingKey') -def signingKeyPwd = findProperty('signingPassword') +import me.champeau.gradle.japicmp.JapicmpTask -oss { - name "java jwt" - repository "java-jwt" - organization "auth0" - description "Java implementation of JSON Web Token (JWT)" - baselineCompareVersion "4.1.0" - skipAssertSigningConfiguration true +project.afterEvaluate { - developers { - auth0 { - displayName = "Auth0" - email = "oss@auth0.com" + def versions = project.ext.testInJavaVersions + for (pluginJavaTestVersion in versions) { + def taskName = "testInJava-${pluginJavaTestVersion}" + tasks.register(taskName, Test) { + def versionToUse = taskName.split("-").getAt(1) as Integer + description = "Runs unit tests on Java version ${versionToUse}." + project.logger.quiet("Test will be running in ${versionToUse}") + group = 'verification' + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(versionToUse) + }) + shouldRunAfter(tasks.named('test')) } - lbalmaceda { - displayName = "Luciano Balmaceda" - email = "luciano.balmaceda@auth0.com" + tasks.named('check') { + dependsOn(taskName) } - hzalaz { - displayName = "Hernan Zalazar" - email = "hernan@auth0.com" + } + + project.configure(project) { + def baselineVersion = project.ext.baselineCompareVersion + task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { + oldClasspath = files(getBaselineJar(project, baselineVersion)) + newClasspath = files(jar.archiveFile) + onlyModified = true + failOnModification = true + ignoreMissingClasses = true + htmlOutputFile = file("$buildDir/reports/apiDiff/apiDiff.html") + txtOutputFile = file("$buildDir/reports/apiDiff/apiDiff.txt") + doLast { + project.logger.quiet("Comparing against baseline version ${baselineVersion}") + } + } + } +} + +private static File getBaselineJar(Project project, String baselineVersion) { + // Use detached configuration: https://github.com/square/okhttp/blob/master/build.gradle#L270 + def group = project.group + try { + def baseline = "${project.group}:${project.name}:$baselineVersion" + project.group = 'virtual_group_for_japicmp' + def dependency = project.dependencies.create(baseline + "@jar") + return project.configurations.detachedConfiguration(dependency).files.find { + it.name == "${project.name}-${baselineVersion}.jar" } + } finally { + project.group = group } } -signing { - useInMemoryPgpKeys(signingKey, signingKeyPwd) +ext { + baselineCompareVersion = '4.1.0' + testInJavaVersions = [8, 11, 17, 21] } java { @@ -198,3 +240,4 @@ tasks.register('jmhHelp', JavaExec) { args '-h' } +apply from: rootProject.file('gradle/maven-publish.gradle') diff --git a/settings.gradle b/settings.gradle index 8d5f112c..d3c4c85b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,9 +2,6 @@ pluginManagement { repositories { gradlePluginPortal() } - plugins { - id 'com.auth0.gradle.oss-library.java' version '0.17.2' - } } include ':java-jwt' From 2643ca2a464dbb159377b67ae32c9e0b7c276b55 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 15:55:30 +0530 Subject: [PATCH 348/355] removed version from gralde properties --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b4d8583f..74a5a049 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,6 @@ org.gradle.jvmargs=-Xmx1536m GROUP=com.auth0 POM_ARTIFACT_ID=java-jwt -VERSION_NAME=4.4.0 POM_NAME=java jwt POM_DESCRIPTION=Java client library for the Auth0 platform From 87710ed5aca11004ee7bc104fb60bb7c5994b0a5 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 19:12:50 +0530 Subject: [PATCH 349/355] updated version --- gradle/maven-publish.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle index 206a581b..a9ad38d3 100644 --- a/gradle/maven-publish.gradle +++ b/gradle/maven-publish.gradle @@ -19,7 +19,7 @@ tasks.withType(Javadoc).configureEach { javadoc { // Specify the Java version that the project will use - options.addStringOption('-release', "8") + options.addStringOption('-release', "11") } artifacts { archives sourcesJar, javadocJar From 65181f8838bcd8cebb8585c8024baba9680d77d9 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 22 Jan 2025 19:47:05 +0530 Subject: [PATCH 350/355] Release 4.5.0 --- .version | 2 +- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 64b5ae39..ae153944 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -4.4.0 \ No newline at end of file +4.5.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1e75bd..6bb634fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-22) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.4.0](https://github.com/auth0/java-jwt/tree/4.4.0) (2023-03-31) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.3.0...4.4.0) diff --git a/README.md b/README.md index 285f56ec..9d0ae41c 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.4.0 + 4.5.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.4.0' +implementation 'com.auth0:java-jwt:4.5.0' ``` ### Create a JWT From 6e76728a92249fd8595e79ee8fdd742a063aa61c Mon Sep 17 00:00:00 2001 From: tanya732 Date: Mon, 27 Jan 2025 12:24:01 +0530 Subject: [PATCH 351/355] upgraded plugin --- lib/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 167f234a..83093fc1 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -13,7 +13,7 @@ plugins { id 'java' id 'jacoco' id 'checkstyle' - id 'me.champeau.gradle.japicmp' version '0.2.9' + id 'me.champeau.gradle.japicmp' version '0.4.1' } sourceSets { @@ -70,8 +70,8 @@ project.afterEvaluate { project.configure(project) { def baselineVersion = project.ext.baselineCompareVersion task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { - oldClasspath = files(getBaselineJar(project, baselineVersion)) - newClasspath = files(jar.archiveFile) + oldClasspath.from(files(getBaselineJar(project, baselineVersion))) + newClasspath.from(files(jar.archiveFile)) onlyModified = true failOnModification = true ignoreMissingClasses = true @@ -122,6 +122,7 @@ javadoc { } dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.15.4' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.4' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' From dcbc560d44903fb47012b291beebad60fe37da73 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 28 Jan 2025 10:58:19 +0530 Subject: [PATCH 352/355] added JAVA_HOME path --- .github/actions/maven-publish/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index 0d280cbe..88fdaa1a 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -26,6 +26,8 @@ runs: source "/home/runner/.sdkman/bin/sdkman-init.sh" sdk list java sdk install java ${{ inputs.java-version }} && sdk default java ${{ inputs.java-version }} + export JAVA_HOME=${SDKMAN_DIR}/candidates/java/current + echo "JAVA_HOME is set to $JAVA_HOME" - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 From ca4f842509e7523b9a6e0f94b1f9b02e8509be93 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 28 Jan 2025 11:16:45 +0530 Subject: [PATCH 353/355] Release 4.5.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bb634fb..577ccf1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-28) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Upgraded Plugin [\#711](https://github.com/auth0/java-jwt/pull/711) ([tanya732](https://github.com/tanya732)) +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-22) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) From 05bc0027837cf85be597efbea51ce7b2104136d9 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 29 Jan 2025 03:09:12 +0530 Subject: [PATCH 354/355] added JAVA_HOME --- .github/actions/maven-publish/action.yml | 3 +++ .github/workflows/java-release.yml | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/.github/actions/maven-publish/action.yml b/.github/actions/maven-publish/action.yml index 88fdaa1a..01e3a621 100644 --- a/.github/actions/maven-publish/action.yml +++ b/.github/actions/maven-publish/action.yml @@ -30,11 +30,14 @@ runs: echo "JAVA_HOME is set to $JAVA_HOME" - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # pin@1.1.0 + env: + JAVA_HOME: ${{ env.JAVA_HOME }} - name: Publish Android/Java Packages to Maven shell: bash run: ./gradlew publish -PisSnapshot=false --stacktrace env: + JAVA_HOME: ${{ env.JAVA_HOME }} MAVEN_USERNAME: ${{ inputs.ossr-username }} MAVEN_PASSWORD: ${{ inputs.ossr-token }} SIGNING_KEY: ${{ inputs.signing-key}} diff --git a/.github/workflows/java-release.yml b/.github/workflows/java-release.yml index ddce3e59..00771307 100644 --- a/.github/workflows/java-release.yml +++ b/.github/workflows/java-release.yml @@ -63,6 +63,12 @@ jobs: - if: steps.tag_exists.outputs.exists == 'true' run: exit 1 + # Set JAVA_HOME here and pass it to subsequent steps + - name: Set JAVA_HOME for Gradle + run: echo "JAVA_HOME=/home/runner/.sdkman/candidates/java/current" >> $GITHUB_ENV # This ensures JAVA_HOME is set globally + env: + SDKMAN_DIR: /home/runner/.sdkman + # Publish the release to Maven - uses: ./.github/actions/maven-publish with: @@ -71,6 +77,8 @@ jobs: ossr-token: ${{ secrets.ossr-token }} signing-key: ${{ secrets.signing-key }} signing-password: ${{ secrets.signing-password }} + env: + JAVA_HOME: ${{ env.JAVA_HOME }} # Create a release for the tag - uses: ./.github/actions/release-create From 051e1c3efba283c5dc6812f7bffb715995d4794c Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 29 Jan 2025 10:21:20 +0530 Subject: [PATCH 355/355] Release 4.5.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 577ccf1b..b97fab71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-29) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0) + +**Added** +- Upgraded Plugin [\#711](https://github.com/auth0/java-jwt/pull/711) ([tanya732](https://github.com/tanya732)) +- Fix jackson vuln [\#705](https://github.com/auth0/java-jwt/pull/705) ([tanya732](https://github.com/tanya732)) +- Fix typo in example code [\#682](https://github.com/auth0/java-jwt/pull/682) ([kasperkarlsson](https://github.com/kasperkarlsson)) +- Remove dead README links [\#676](https://github.com/auth0/java-jwt/pull/676) ([jimmyjames](https://github.com/jimmyjames)) +- Fix typo on a comment in JWTCreator.java [\#672](https://github.com/auth0/java-jwt/pull/672) ([sgc109](https://github.com/sgc109)) +- Remove CircleCI [\#670](https://github.com/auth0/java-jwt/pull/670) ([jimmyjames](https://github.com/jimmyjames)) +- Empty string audience claim should be deserialized as empty string [\#663](https://github.com/auth0/java-jwt/pull/663) ([jimmyjames](https://github.com/jimmyjames)) + +**Fixed** +- empty expected audience array should throw InvalidClaimException [\#679](https://github.com/auth0/java-jwt/pull/679) ([jimmyjames](https://github.com/jimmyjames)) + ## [4.5.0](https://github.com/auth0/java-jwt/tree/4.5.0) (2025-01-28) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0)