From 1395fbdccdffedc774877c46062e69631f4914cc Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 16:11:36 -0300 Subject: [PATCH 01/92] First commit. --- .gitignore | 165 ++++++++++++++++++ LICENSE.md | 20 +++ README.md | 48 +++++ pom.xml | 33 ++++ src/main/java/com/auth0/jwt/JWTVerifier.java | 153 ++++++++++++++++ .../java/com/auth0/jwt/JWTVerifierTest.java | 151 ++++++++++++++++ 6 files changed, 570 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/auth0/jwt/JWTVerifier.java create mode 100644 src/test/java/com/auth0/jwt/JWTVerifierTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..54b2dc3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +node_modules + +# Ignore Java and IntelliJ IDEA stuff +.idea +target +*.iml + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +packages + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..5a27aaff --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 AUTH10 LLC + +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..67995a11 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Java JWT + +An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). + +This was developed against draft-ietf-oauth-json-web-token-08 + +### Usage + +```java + public class Application { + public static void main (String [] args) { + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } + } +`` + +### FAQ + + +#### Why another JSON Web Token implementation for Java? +We think that current JWT implementations are either too complex or not enough tested. We want something simple with the right number of abstractions. + +## License + +The MIT License (MIT) + +Copyright (c) 2013 AUTH10 LLC + +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. diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..b126c993 --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + com.auth0 + java-jwt + 0.1-SNAPSHOT + + + + + com.fasterxml.jackson.core + jackson-databind + 2.0.0 + + + commons-codec + commons-codec + 1.4 + + + + junit + junit + 4.11 + test + + + + + \ No newline at end of file diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java new file mode 100644 index 00000000..8f8e1f1f --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -0,0 +1,153 @@ +package com.auth0.jwt; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * JWT Java Implementation + *

+ * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home + * See JWTVerifier.java + */ +public class JWTVerifier { + + private final String secret; + private final String audience; + private final String issuer; + private final Base64 decoder; + + private final ObjectMapper mapper; + + private Map algorithms; + + public JWTVerifier(String secret, String audience, String issuer) { + if (secret == null || "".equals(secret)) { + throw new IllegalArgumentException("Secret cannot be null or empty"); + } + + decoder = new Base64(true); + mapper = new ObjectMapper(); + + algorithms = new HashMap(); + algorithms.put("HS256", "HmacSHA256"); + algorithms.put("HS384", "HmacSHA384"); + algorithms.put("HS512", "HmacSHA512"); + + this.secret = secret; + this.audience = audience; + this.issuer = issuer; + } + + public JWTVerifier(String secret, String audience) { + this(secret, audience, null); + } + + public JWTVerifier(String secret) { + this(secret, null, null); + } + + /** + * Performs JWT validation + * + * @param token token to verify + * @throws SignatureException when signature is invalid + * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid + */ + public Map verify(String token) + throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, + IOException, SignatureException { + if (token == null || "".equals(token)) { + throw new IllegalStateException("token not set"); + } + + String[] pieces = token.split("\\."); + + // check number of segments + if (pieces.length != 3) { + throw new IllegalStateException("Wrong number of segments: " + pieces.length); + } + + // get JWTHeader JSON object. Extract algorithm + Map jwtHeader = decodeAndParse(pieces[0]); + + String algorithm = getAlgorithm(jwtHeader); + + // get JWTClaims JSON object + Map jwtPayload = decodeAndParse(pieces[1]); + + // check signature + verifySignature(pieces, algorithm); + + // additional JWTClaims checks + verifyExpiration(jwtPayload); + verifyIssuer(jwtPayload); + verifyAudience(jwtPayload); + + return jwtPayload; + } + + void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Mac hmac = Mac.getInstance(algorithm); + hmac.init(new SecretKeySpec(decoder.decodeBase64(secret), algorithm)); + byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); + + if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { + throw new SignatureException("signature verification failed"); + } + } + + void verifyExpiration(Map jwtClaims) { + long expiration = Long.parseLong(jwtClaims.get("exp")); + if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { + throw new IllegalStateException("jwt expired"); + } + } + + void verifyIssuer(Map jwtClaims) { + String issuerFromToken = jwtClaims.get("iss"); + + if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { + throw new IllegalStateException("jwt issuer invalid"); + } + } + + void verifyAudience(Map jwtClaims) { + String audienceFromToken = jwtClaims.get("aud"); + + if (audienceFromToken != null && !audience.equals(audienceFromToken)) { + throw new IllegalStateException("jwt audience invalid"); + } + } + + String getAlgorithm(Map jwtHeader) { + String algorithmName = jwtHeader.get("alg"); + + if (jwtHeader.get("alg") == null) { + throw new IllegalStateException("algorithm not set"); + } + + if (algorithms.get(algorithmName) == null) { + throw new IllegalStateException("unsupported algorithm"); + } + + return algorithms.get(algorithmName); + } + + Map decodeAndParse(String b64String) throws IOException { + String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); + TypeReference> typeRef = new TypeReference< HashMap >() {}; + Map jwtHeader = mapper.readValue(jsonString, typeRef); + return jwtHeader; + } +} \ No newline at end of file diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java new file mode 100644 index 00000000..dd1e031f --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -0,0 +1,151 @@ +package com.auth0.jwt; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Test; + +import java.security.SignatureException; +import java.util.Collections; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class JWTVerifierTest { + + @Test(expected = IllegalArgumentException.class) + public void constructorShouldFailOnNullSecret() { + new JWTVerifier(null); + } + + @Test(expected = IllegalArgumentException.class) + public void constructorShouldFailOnEmptySecret() { + new JWTVerifier(""); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn1Segments() throws Exception { + new JWTVerifier("such secret").verify("crypto"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn2Segments() throws Exception { + new JWTVerifier("such secret").verify("much.crypto"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn4Segments() throws Exception { + new JWTVerifier("such secret").verify("much.crypto.so.token"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOnEmptyStringToken() throws Exception { + new JWTVerifier("such secret").verify(""); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOnNullToken() throws Exception { + new JWTVerifier("such secret").verify(null); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.emptyMap()); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIfAlgorithmIsNotSupported() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "doge-crypt")); + } + + @Test + public void shouldWorkIfAlgorithmIsSupported() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "HS256")); + } + + @Test(expected = SignatureException.class) + public void shouldFailOnInvalidSignature() throws Exception { + final String jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + + "." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + + "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + + "." + + "suchsignature_plzvalidate_zomgtokens"; + String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + new JWTVerifier(secret, "audience").verifySignature(jws.split("\\."), "HmacSHA256"); + } + + @Test + public void shouldVerifySignature() throws Exception { + final String jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + + "." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + + "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + + "." + + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + final String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + new JWTVerifier(secret, "audience") + .verifySignature(jws.split("\\."), "HmacSHA256"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailWhenExpired1SecondAgo() throws Exception { + new JWTVerifier("such secret").verifyExpiration( + Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); + } + + @Test + public void shouldVerifyExpiration() throws Exception { + new JWTVerifier("such secret").verifyExpiration( + Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); + } + + @Test + public void shouldVerifyIssuer() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.singletonMap("iss", "very issuer")); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIssuer() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.singletonMap("iss", "wow")); + } + + @Test + public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.emptyMap()); + } + + @Test + public void shouldVerifyAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.singletonMap("aud", "amaze audience")); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.singletonMap("aud", "wow")); + } + + @Test + public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.emptyMap()); + } + + @Test + public void decodeAndParse() throws Exception { + final Base64 encoder = new Base64(true); + final String encodedJSON = new String(encoder.encode("{\"some\": \"json\", \"number\": 123}".getBytes())); + final JWTVerifier jwtVerifier = new JWTVerifier("secret", "audience"); + + final Map decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); + + assertEquals("json", decodedJSON.get("some")); + assertEquals(null, decodedJSON.get("unexisting_property")); + assertEquals("123", decodedJSON.get("number")); + } + + +} From e36522a977b40a24c682d5d1090fbd0578b6d816 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:12:58 -0200 Subject: [PATCH 02/92] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67995a11..3743bc2c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). -This was developed against draft-ietf-oauth-json-web-token-08 +This was developed against `draft-ietf-oauth-json-web-token-08`. ### Usage @@ -15,7 +15,7 @@ This was developed against draft-ietf-oauth-json-web-token-08 System.out.println(decodedPayload.get("name")); } } -`` +``` ### FAQ From 2e234db0b940c790f1f17cb86becce558c740ccc Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:15:25 -0200 Subject: [PATCH 03/92] Update README.md --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3743bc2c..04244eda 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,16 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. ```java public class Application { public static void main (String [] args) { - Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); - - // Get custom fields from decoded Payload - System.out.println(decodedPayload.get("name")); + try { + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } catch (SignatureException signatureException) { + System.err.println("Invalid signature!"); + } catch (IllegalStateException illegalStateException) { + System.err.println("Invalid Token! " + illegalStateException); + } } } ``` From 25959995a9dfb03f6fa303ea6eb7ef9b7454b19d Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:15:47 -0200 Subject: [PATCH 04/92] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 04244eda..5ef74416 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. public class Application { public static void main (String [] args) { try { - Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + Map decodedPayload = + new JWTVerifier("secret", "audience").verify("my-token"); // Get custom fields from decoded Payload System.out.println(decodedPayload.get("name")); From ef353340662460015ca6957174aefa3e3b1082a9 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 16:59:11 -0300 Subject: [PATCH 05/92] Making verify method return Map --- README.md | 2 +- src/main/java/com/auth0/jwt/JWTVerifier.java | 29 +++++++-------- .../java/com/auth0/jwt/JWTVerifierTest.java | 36 +++++++++++-------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5ef74416..c1c05d63 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. public class Application { public static void main (String [] args) { try { - Map decodedPayload = + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); // Get custom fields from decoded Payload diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 8f8e1f1f..7ecb3758 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,6 +1,7 @@ package com.auth0.jwt; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; @@ -79,12 +80,12 @@ public Map verify(String token) } // get JWTHeader JSON object. Extract algorithm - Map jwtHeader = decodeAndParse(pieces[0]); + JsonNode jwtHeader = decodeAndParse(pieces[0]); String algorithm = getAlgorithm(jwtHeader); // get JWTClaims JSON object - Map jwtPayload = decodeAndParse(pieces[1]); + JsonNode jwtPayload = decodeAndParse(pieces[1]); // check signature verifySignature(pieces, algorithm); @@ -94,7 +95,7 @@ public Map verify(String token) verifyIssuer(jwtPayload); verifyAudience(jwtPayload); - return jwtPayload; + return mapper.treeToValue(jwtPayload, Map.class); } void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { @@ -107,31 +108,32 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx } } - void verifyExpiration(Map jwtClaims) { - long expiration = Long.parseLong(jwtClaims.get("exp")); + void verifyExpiration(JsonNode jwtClaims) { + final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; + if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { throw new IllegalStateException("jwt expired"); } } - void verifyIssuer(Map jwtClaims) { - String issuerFromToken = jwtClaims.get("iss"); + void verifyIssuer(JsonNode jwtClaims) { + final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { throw new IllegalStateException("jwt issuer invalid"); } } - void verifyAudience(Map jwtClaims) { - String audienceFromToken = jwtClaims.get("aud"); + void verifyAudience(JsonNode jwtClaims) { + final String audienceFromToken = jwtClaims.has("aud") ? jwtClaims.get("aud").asText() : null; if (audienceFromToken != null && !audience.equals(audienceFromToken)) { throw new IllegalStateException("jwt audience invalid"); } } - String getAlgorithm(Map jwtHeader) { - String algorithmName = jwtHeader.get("alg"); + String getAlgorithm(JsonNode jwtHeader) { + final String algorithmName = jwtHeader.has("alg") ? jwtHeader.get("alg").asText() : null; if (jwtHeader.get("alg") == null) { throw new IllegalStateException("algorithm not set"); @@ -144,10 +146,9 @@ String getAlgorithm(Map jwtHeader) { return algorithms.get(algorithmName); } - Map decodeAndParse(String b64String) throws IOException { + JsonNode decodeAndParse(String b64String) throws IOException { String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); - TypeReference> typeRef = new TypeReference< HashMap >() {}; - Map jwtHeader = mapper.readValue(jsonString, typeRef); + JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } } \ No newline at end of file diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index dd1e031f..fa8b3205 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1,5 +1,8 @@ package com.auth0.jwt; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.codec.binary.Base64; import org.junit.Test; @@ -48,17 +51,17 @@ public void shouldFailOnNullToken() throws Exception { @Test(expected = IllegalStateException.class) public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.emptyMap()); + new JWTVerifier("such secret").getAlgorithm(JsonNodeFactory.instance.objectNode()); } @Test(expected = IllegalStateException.class) public void shouldFailIfAlgorithmIsNotSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "doge-crypt")); + new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "doge-crypt")); } @Test public void shouldWorkIfAlgorithmIsSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "HS256")); + new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "HS256")); } @Test(expected = SignatureException.class) @@ -89,49 +92,49 @@ public void shouldVerifySignature() throws Exception { @Test(expected = IllegalStateException.class) public void shouldFailWhenExpired1SecondAgo() throws Exception { new JWTVerifier("such secret").verifyExpiration( - Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); + createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); } @Test public void shouldVerifyExpiration() throws Exception { new JWTVerifier("such secret").verifyExpiration( - Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); + createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); } @Test public void shouldVerifyIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.singletonMap("iss", "very issuer")); + .verifyIssuer(createSingletonJSONNode("iss", "very issuer")); } @Test(expected = IllegalStateException.class) public void shouldFailIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.singletonMap("iss", "wow")); + .verifyIssuer(createSingletonJSONNode("iss", "wow")); } @Test public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.emptyMap()); + .verifyIssuer(JsonNodeFactory.instance.objectNode()); } @Test public void shouldVerifyAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.singletonMap("aud", "amaze audience")); + .verifyAudience(createSingletonJSONNode("aud", "amaze audience")); } @Test(expected = IllegalStateException.class) public void shouldFailAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.singletonMap("aud", "wow")); + .verifyAudience(createSingletonJSONNode("aud", "wow")); } @Test public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.emptyMap()); + .verifyAudience(JsonNodeFactory.instance.objectNode()); } @Test @@ -140,12 +143,17 @@ public void decodeAndParse() throws Exception { final String encodedJSON = new String(encoder.encode("{\"some\": \"json\", \"number\": 123}".getBytes())); final JWTVerifier jwtVerifier = new JWTVerifier("secret", "audience"); - final Map decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); + final JsonNode decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); - assertEquals("json", decodedJSON.get("some")); + assertEquals("json", decodedJSON.get("some").asText()); assertEquals(null, decodedJSON.get("unexisting_property")); - assertEquals("123", decodedJSON.get("number")); + assertEquals("123", decodedJSON.get("number").asText()); } + public static JsonNode createSingletonJSONNode(String key, String value) { + final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); + jsonNodes.put(key, value); + return jsonNodes; + } } From 1d2ac94ed069b937fddf2e982e24fe0a1f57cc90 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:58:59 -0200 Subject: [PATCH 06/92] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c1c05d63..340f3f24 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Java JWT -An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). - -This was developed against `draft-ietf-oauth-json-web-token-08`. +An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. ### Usage From b7e79dc8711c7d293deae22b22d2867b09c0407e Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:05:03 -0300 Subject: [PATCH 07/92] Making changes to pom.xml for release. --- pom.xml | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pom.xml b/pom.xml index b126c993..ef60a518 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,48 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.sonatype.oss + oss-parent + 9 + + com.auth0 java-jwt 0.1-SNAPSHOT + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io + + + 1.6 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + + + + + Alberto Pose + pose + + Developer + + + + + + https://github.com/auth0/java-jwt + scm:git:git@github.com:auth0/java-jwt.git + scm:git:git@github.com:auth0/java-jwt.git + + @@ -29,5 +67,18 @@ + + + + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + UTF-8 + + + + \ No newline at end of file From efe0630b87a3d240a93851fc8e760e6c432c0c43 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:05:58 -0300 Subject: [PATCH 08/92] Adding Maven Coordinates section. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 340f3f24..6840c908 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,19 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o } ``` +#### Maven coordinates? + +Yes, here you are: + +```xml + + com.auth0 + java-jwt + 0.1 + +``` + + ### FAQ From 90b0f883329acac7616bff05f858618938e368e8 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:08:49 -0300 Subject: [PATCH 09/92] [maven-release-plugin] prepare release java-jwt-0.1 --- pom.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index ef60a518..21b3bb2f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 @@ -12,7 +10,7 @@ com.auth0 java-jwt - 0.1-SNAPSHOT + 0.1 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 72f8707f7a649aa3070f3ab828f53daff1d7a989 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:08:56 -0300 Subject: [PATCH 10/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21b3bb2f..efa6c134 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.1 + 0.2-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 90c7313a96b7c284de6e4d9bc7a70cef3633506e Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Fri, 7 Mar 2014 19:13:34 -0200 Subject: [PATCH 11/92] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6840c908..2ed3675b 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ Yes, here you are: ``` +### Credits + +Most of the code have been written by Luis Faja . We just wrapped it on a nicer interface and published it to maven. We'll be adding support for signing and other algorithms in the future. ### FAQ From 65af61b0fbae3f542adbdef471387f982c7751ac Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:27:17 -0300 Subject: [PATCH 12/92] Oops, changing wrong verify method signature. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 7ecb3758..140291df 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -65,7 +65,7 @@ public JWTVerifier(String secret) { * @throws SignatureException when signature is invalid * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid */ - public Map verify(String token) + public Map verify(String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, IOException, SignatureException { if (token == null || "".equals(token)) { From 9035adcb11d1f56b46a4c53723cf3b2b941818db Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:27:57 -0300 Subject: [PATCH 13/92] [maven-release-plugin] prepare release java-jwt-0.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index efa6c134..fda1bf84 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.2-SNAPSHOT + 0.2 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From e2c8d541bf001617ef3e9a5034452eec47b1553a Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:28:03 -0300 Subject: [PATCH 14/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fda1bf84..db2b48a2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.2 + 0.3-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 722f939f718e446f15843517ecba8859f88245e8 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 12:37:57 -0200 Subject: [PATCH 15/92] Version bump --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ed3675b..e467584a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.1 + 0.2 ``` From d97997761f3b189d331d5533e89526631aadf763 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 9 Mar 2014 15:13:51 -0200 Subject: [PATCH 16/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e467584a..fa341a44 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ We think that current JWT implementations are either too complex or not enough t The MIT License (MIT) -Copyright (c) 2013 AUTH10 LLC +Copyright (c) 2014 AUTH10 LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9d4da17549079bf341e39b27e319bf95bfb0e3b0 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Mon, 10 Mar 2014 12:55:17 -0200 Subject: [PATCH 17/92] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 5a27aaff..c1bf8861 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 AUTH10 LLC +Copyright (c) 2013 Auth0, Inc 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 From 18e771412b27bbc3397b49af09f5d6915c0fdf27 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Mon, 10 Mar 2014 12:55:44 -0200 Subject: [PATCH 18/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa341a44..a0e21a31 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ We think that current JWT implementations are either too complex or not enough t The MIT License (MIT) -Copyright (c) 2014 AUTH10 LLC +Copyright (c) 2014 Auth0, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a7391e46a2db396adc0ca30dae1a88c7fa7862df Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 10 Mar 2014 13:15:55 -0200 Subject: [PATCH 19/92] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index c1bf8861..8258c89c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Auth0, Inc +Copyright (c) 2014 Auth0, Inc 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 From 17f70e9c7ea031e5f6641cadc65c4b0780a5045b Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 3 May 2014 10:37:32 -0300 Subject: [PATCH 20/92] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a0e21a31..14404e70 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o ### Usage ```java - public class Application { - public static void main (String [] args) { - try { - Map decodedPayload = - new JWTVerifier("secret", "audience").verify("my-token"); +public class Application { + public static void main (String [] args) { + try { + Map decodedPayload = + new JWTVerifier("secret", "audience").verify("my-token"); - // Get custom fields from decoded Payload - System.out.println(decodedPayload.get("name")); - } catch (SignatureException signatureException) { - System.err.println("Invalid signature!"); - } catch (IllegalStateException illegalStateException) { - System.err.println("Invalid Token! " + illegalStateException); - } + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } catch (SignatureException signatureException) { + System.err.println("Invalid signature!"); + } catch (IllegalStateException illegalStateException) { + System.err.println("Invalid Token! " + illegalStateException); } } +} ``` #### Maven coordinates? From e4271d01ec13f1f0a5a4ce57569687720b008836 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 01:16:45 +0200 Subject: [PATCH 21/92] updated with package provided in auth0/java-jwt#1 by @woloski --- .gitignore | 4 + pom.xml | 159 +++++++++++------- src/main/java/com/auth0/jwt/Algorithm.java | 15 ++ src/main/java/com/auth0/jwt/ClaimSet.java | 14 ++ src/main/java/com/auth0/jwt/JwtProxy.java | 8 + src/main/java/com/auth0/jwt/JwtSigner.java | 141 ++++++++++++++++ .../java/com/auth0/jwt/PayloadHandler.java | 10 ++ src/main/java/com/auth0/jwt/TestHarness.java | 31 ++++ src/main/java/com/auth0/jwt/User.java | 29 ++++ .../auth0/jwt/impl/BasicPayloadHandler.java | 18 ++ .../java/com/auth0/jwt/impl/JwtProxyImpl.java | 55 ++++++ 11 files changed, 424 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/auth0/jwt/Algorithm.java create mode 100644 src/main/java/com/auth0/jwt/ClaimSet.java create mode 100644 src/main/java/com/auth0/jwt/JwtProxy.java create mode 100644 src/main/java/com/auth0/jwt/JwtSigner.java create mode 100644 src/main/java/com/auth0/jwt/PayloadHandler.java create mode 100644 src/main/java/com/auth0/jwt/TestHarness.java create mode 100644 src/main/java/com/auth0/jwt/User.java create mode 100644 src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java create mode 100644 src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java diff --git a/.gitignore b/.gitignore index 54b2dc3f..2e9d0dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ node_modules +# Ignore Eclipse stuff +.project +.settings + # Ignore Java and IntelliJ IDEA stuff .idea target diff --git a/pom.xml b/pom.xml index db2b48a2..c06db800 100644 --- a/pom.xml +++ b/pom.xml @@ -1,50 +1,13 @@ - - - 4.0.0 + + 4.0.0 + com.auth0 + java-jwt-signer + 1.0 + jar + JWT Signer - Project Object Model - - org.sonatype.oss - oss-parent - 9 - - - com.auth0 - java-jwt - 0.3-SNAPSHOT - - Java JWT - Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. - http://www.jwt.io - - - 1.6 - - - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - repo - - - - - - Alberto Pose - pose - - Developer - - - - - - https://github.com/auth0/java-jwt - scm:git:git@github.com:auth0/java-jwt.git - scm:git:git@github.com:auth0/java-jwt.git - - - + com.fasterxml.jackson.core @@ -65,18 +28,94 @@ - - - - maven-compiler-plugin - 3.1 - - ${java.version} - ${java.version} - UTF-8 - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.codehaus.mojo + properties-maven-plugin + 1.0-alpha-2 + + + initialize + + read-project-properties + + + true + + ${basedir}/build.properties + ${basedir}/build.local.properties + + + + + + + + src/main/java + + + + src/main/resources + true + + **/*.xml + **/*.properties + + + - \ No newline at end of file + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.codehaus.mojo + + + properties-maven-plugin + + + [1.0-alpha-2,) + + + + read-project-properties + + + + + + false + + + + + + + + + + + diff --git a/src/main/java/com/auth0/jwt/Algorithm.java b/src/main/java/com/auth0/jwt/Algorithm.java new file mode 100644 index 00000000..be135d35 --- /dev/null +++ b/src/main/java/com/auth0/jwt/Algorithm.java @@ -0,0 +1,15 @@ +package com.auth0.jwt; + +public enum Algorithm { + HS256("HmacSHA256"), HS384("HmacSHA384"), HS512("HmacSHA512"), RS256("RS256"), RS384("RS384"), RS512("RS512"); + + private Algorithm(String value) { + this.value = value; + } + + private String value; + + public String getValue() { + return value; + } +} diff --git a/src/main/java/com/auth0/jwt/ClaimSet.java b/src/main/java/com/auth0/jwt/ClaimSet.java new file mode 100644 index 00000000..8c401684 --- /dev/null +++ b/src/main/java/com/auth0/jwt/ClaimSet.java @@ -0,0 +1,14 @@ +package com.auth0.jwt; + +public class ClaimSet { + + private int exp; + + public int getExp() { + return exp; + } + + public void setExp(int exp) { + this.exp = (int)(System.currentTimeMillis() / 1000L) + exp; + } +} diff --git a/src/main/java/com/auth0/jwt/JwtProxy.java b/src/main/java/com/auth0/jwt/JwtProxy.java new file mode 100644 index 00000000..da7c7f3d --- /dev/null +++ b/src/main/java/com/auth0/jwt/JwtProxy.java @@ -0,0 +1,8 @@ +package com.auth0.jwt; + +public interface JwtProxy { + + void setPayloadHandler(PayloadHandler payloadHandler); + String encode(Algorithm algorithm, Object obj, String secret, ClaimSet claimSet) throws Exception; + Object decode(Algorithm algorithm, String token, String secret) throws Exception; +} diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java new file mode 100644 index 00000000..18123e0f --- /dev/null +++ b/src/main/java/com/auth0/jwt/JwtSigner.java @@ -0,0 +1,141 @@ +package com.auth0.jwt; + +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JwtSigner implementation based on the Ruby implementation from http://jwt.io + * No support for RSA encryption at present + */ +public class JwtSigner { + + /** + * Generate a JSON web token based on a payload, secret key and claim set + */ + public String encode(Algorithm algorithm, String payload, String payloadId, String key, + ClaimSet claimSet) throws Exception { + + List segments = new ArrayList(); + + segments.add(encodedHeader(algorithm)); + segments.add(encodedPayload(payload, payloadId, claimSet)); + segments.add(encodedSignature(join(segments, "."), key, algorithm)); + + return join(segments, "."); + } + + /** + * Generate the header part of a JSON web token + */ + private String encodedHeader(Algorithm algorithm) + throws Exception { + + if (algorithm == null) { // default the algorithm if not specified + algorithm = Algorithm.HS256; + } + + // create the header + ObjectNode header = JsonNodeFactory.instance.objectNode(); + header.put("type", "JWT"); + header.put("alg", algorithm.name()); + + return base64UrlEncode(header.toString().getBytes()); + } + + /** + * Generate the JSON web token payload, merging it with the claim set + */ + private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { + + ObjectNode _claimSet = JsonNodeFactory.instance.objectNode(); + ObjectNode _payload = JsonNodeFactory.instance.objectNode();; + + _payload.put(payloadId, payload); + + if(claimSet != null) { + if(claimSet.getExp() > 0) { + _claimSet.put("exp", claimSet.getExp()); + } + _payload.putAll(_claimSet); + } + + return base64UrlEncode(_payload.toString().getBytes()); + } + + /** + * Sign the header and payload + */ + private String encodedSignature(String signingInput, String key, + Algorithm algorithm) throws Exception { + + byte[] signature = sign(algorithm, signingInput, key); + return base64UrlEncode(signature); + } + + /** + * Safe URL encode a byte array to a String + */ + private String base64UrlEncode(byte[] str) throws Exception { + + return new String(Base64.encodeBase64URLSafe(str)); + } + + /** + * Switch the signing algorithm based on input, RSA not supported + */ + private byte[] sign(Algorithm algorithm, String msg, String key) + throws Exception { + + switch (algorithm) { + case HS256: + case HS384: + case HS512: + return signHmac(algorithm, msg, key); + case RS256: + case RS384: + case RS512: + default: + throw new OperationNotSupportedException( + "Unsupported signing method"); + } + } + + /** + * Sign an input string using HMAC and return the encrypted bytes + */ + private byte[] signHmac(Algorithm algorithm, String msg, String key) + throws Exception { + + Mac mac = Mac.getInstance(algorithm.getValue()); + mac.init(new SecretKeySpec(key.getBytes(), algorithm.getValue())); + return mac.doFinal(msg.getBytes()); + } + + /** + * Mimick the ruby array.join function + */ + private String join(List input, String on) { + + int size = input.size(); + int count = 1; + StringBuilder joined = new StringBuilder(); + for (String string : input) { + joined.append(string); + if (count < size) { + joined.append(on); + } + count++; + } + + return joined.toString(); + } +} diff --git a/src/main/java/com/auth0/jwt/PayloadHandler.java b/src/main/java/com/auth0/jwt/PayloadHandler.java new file mode 100644 index 00000000..24fd8eca --- /dev/null +++ b/src/main/java/com/auth0/jwt/PayloadHandler.java @@ -0,0 +1,10 @@ +package com.auth0.jwt; + +/** + * Abstraction to allow custom payload handling e.g. in the event the payload needs to be encrypted + */ +public interface PayloadHandler { + + String encoding(Object payload) throws Exception; + Object decoding(String payload) throws Exception; +} diff --git a/src/main/java/com/auth0/jwt/TestHarness.java b/src/main/java/com/auth0/jwt/TestHarness.java new file mode 100644 index 00000000..a7f5da9e --- /dev/null +++ b/src/main/java/com/auth0/jwt/TestHarness.java @@ -0,0 +1,31 @@ +package com.auth0.jwt; + +import com.auth0.jwt.impl.BasicPayloadHandler; +import com.auth0.jwt.impl.JwtProxyImpl; + +/** + * Test harness for JwtProxy + */ +public class TestHarness { + + public static void main(String[] args) throws Exception { + + final String secret = "This is a secret"; + final Algorithm algorithm = Algorithm.HS256; + + User user = new User(); + user.setUsername("jwt"); + user.setPassword("mypassword"); + + JwtProxy proxy = new JwtProxyImpl(); + proxy.setPayloadHandler(new BasicPayloadHandler()); + + ClaimSet claimSet = new ClaimSet(); + claimSet.setExp(24 * 60 * 60); // expire in 24 hours + String token = proxy.encode(algorithm, user, secret, claimSet); + System.out.println(token); + + Object payload = proxy.decode(algorithm, token, secret); + System.out.println(payload); + } +} diff --git a/src/main/java/com/auth0/jwt/User.java b/src/main/java/com/auth0/jwt/User.java new file mode 100644 index 00000000..dc50b2b5 --- /dev/null +++ b/src/main/java/com/auth0/jwt/User.java @@ -0,0 +1,29 @@ +package com.auth0.jwt; + +/** + * Sample object for serialization + */ +public class User { + + private String username; + private String password; + + public User() { + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java new file mode 100644 index 00000000..e198a28a --- /dev/null +++ b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java @@ -0,0 +1,18 @@ +package com.auth0.jwt.impl; + +import com.auth0.jwt.PayloadHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Basic implementation of a payload handler which serializes the payload to a String, and echoes it for deserialization + */ +public final class BasicPayloadHandler implements PayloadHandler { + + public String encoding(Object payload) throws Exception { + return new ObjectMapper().writeValueAsString(payload); + } + + public Object decoding(String payload) throws Exception { + return payload; + } +} diff --git a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java new file mode 100644 index 00000000..ba0e7a6b --- /dev/null +++ b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java @@ -0,0 +1,55 @@ +package com.auth0.jwt.impl; + +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; + +import com.auth0.jwt.Algorithm; +import com.auth0.jwt.ClaimSet; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.JwtProxy; +import com.auth0.jwt.JwtSigner; +import com.auth0.jwt.PayloadHandler; + +/** + * JwtProxy implementation + */ +public class JwtProxyImpl implements JwtProxy { + + // the payload identifier in the JSON object + private static final String PAYLOAD_ID = "payload"; + private PayloadHandler payloadHandler; + + public void setPayloadHandler(PayloadHandler payloadHandler) { + this.payloadHandler = payloadHandler; + } + + public PayloadHandler getPayloadHandler() { + return payloadHandler; + } + + /** + * Create a JSON web token by serializing a java object + */ + public String encode(Algorithm algorithm, Object obj, String secret, + ClaimSet claimSet) throws Exception { + + JwtSigner jwtSigner = new JwtSigner(); + String payload = getPayloadHandler().encoding(obj); + + return jwtSigner.encode(algorithm, payload, PAYLOAD_ID, secret, claimSet); + } + + /** + * Verify a JSON web token and return the object serialized in the JSON payload + */ + public Object decode(Algorithm algorithm, String token, String secret) + throws Exception { + + JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(secret.getBytes())); + Map verify = jwtVerifier.verify(token); + String payload = (String) verify.get(PAYLOAD_ID); + + return getPayloadHandler().decoding(payload); + } +} From 168d288a3c139dc809aa9a462203082734c24d8e Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 01:22:32 +0200 Subject: [PATCH 22/92] took some infos from previous pom --- pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pom.xml b/pom.xml index c06db800..5590bd93 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,24 @@ 1.0 jar JWT Signer - Project Object Model + + + org.sonatype.oss + oss-parent + 9 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + + + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io From efe867863bab18476f78d06924378754e39d5377 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 02:10:34 +0200 Subject: [PATCH 23/92] moved TestHarness to the test folder --- pom.xml | 3 +-- src/{main => test}/java/com/auth0/jwt/TestHarness.java | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/{main => test}/java/com/auth0/jwt/TestHarness.java (90%) diff --git a/pom.xml b/pom.xml index 5590bd93..97eea9bd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ java-jwt-signer 1.0 jar - JWT Signer - Project Object Model + Java JWT org.sonatype.oss @@ -21,7 +21,6 @@ - Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io diff --git a/src/main/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java similarity index 90% rename from src/main/java/com/auth0/jwt/TestHarness.java rename to src/test/java/com/auth0/jwt/TestHarness.java index a7f5da9e..b48e0596 100644 --- a/src/main/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -2,13 +2,15 @@ import com.auth0.jwt.impl.BasicPayloadHandler; import com.auth0.jwt.impl.JwtProxyImpl; +import org.junit.Test; /** * Test harness for JwtProxy */ public class TestHarness { - public static void main(String[] args) throws Exception { + @Test + public void testHarness() throws Exception { final String secret = "This is a secret"; final Algorithm algorithm = Algorithm.HS256; From a0fa787cdec51e728f310c87933aa325ed386c95 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 03:26:54 +0200 Subject: [PATCH 24/92] removed System.out.println occurencies and moved User to test package --- src/test/java/com/auth0/jwt/TestHarness.java | 4 ++-- src/{main => test}/java/com/auth0/jwt/User.java | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{main => test}/java/com/auth0/jwt/User.java (100%) diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java index b48e0596..9024dc50 100644 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -1,5 +1,6 @@ package com.auth0.jwt; +import static org.junit.Assert.*; import com.auth0.jwt.impl.BasicPayloadHandler; import com.auth0.jwt.impl.JwtProxyImpl; import org.junit.Test; @@ -25,9 +26,8 @@ public void testHarness() throws Exception { ClaimSet claimSet = new ClaimSet(); claimSet.setExp(24 * 60 * 60); // expire in 24 hours String token = proxy.encode(algorithm, user, secret, claimSet); - System.out.println(token); Object payload = proxy.decode(algorithm, token, secret); - System.out.println(payload); + assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); } } diff --git a/src/main/java/com/auth0/jwt/User.java b/src/test/java/com/auth0/jwt/User.java similarity index 100% rename from src/main/java/com/auth0/jwt/User.java rename to src/test/java/com/auth0/jwt/User.java From 2024530eafa16e6fefa5a9d0eefc43e7b1f6bcf3 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Mon, 23 Jun 2014 01:26:06 +0200 Subject: [PATCH 25/92] removed double ';' and renamed variable with '_' --- src/main/java/com/auth0/jwt/JwtSigner.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java index 18123e0f..5c1a9694 100644 --- a/src/main/java/com/auth0/jwt/JwtSigner.java +++ b/src/main/java/com/auth0/jwt/JwtSigner.java @@ -56,19 +56,19 @@ private String encodedHeader(Algorithm algorithm) */ private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { - ObjectNode _claimSet = JsonNodeFactory.instance.objectNode(); - ObjectNode _payload = JsonNodeFactory.instance.objectNode();; + ObjectNode localClaimSet = JsonNodeFactory.instance.objectNode(); + ObjectNode localPayload = JsonNodeFactory.instance.objectNode(); - _payload.put(payloadId, payload); + localPayload.put(payloadId, payload); if(claimSet != null) { if(claimSet.getExp() > 0) { - _claimSet.put("exp", claimSet.getExp()); + localClaimSet.put("exp", claimSet.getExp()); } - _payload.putAll(_claimSet); + localPayload.putAll(localClaimSet); } - return base64UrlEncode(_payload.toString().getBytes()); + return base64UrlEncode(localPayload.toString().getBytes()); } /** From 26fc6841cb135f54115640d8f2accde69878d51e Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:08:17 -0300 Subject: [PATCH 26/92] pom.xml cleanup --- pom.xml | 132 +++++++++++++++----------------------------------------- 1 file changed, 34 insertions(+), 98 deletions(-) diff --git a/pom.xml b/pom.xml index 97eea9bd..1c5c98a1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,13 @@ - 4.0.0 - com.auth0 - java-jwt-signer - 1.0 - jar - Java JWT - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + com.auth0 + java-jwt-signer + 1.0 + jar + Java JWT + + org.sonatype.oss oss-parent 9 @@ -24,7 +24,7 @@ Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io - + com.fasterxml.jackson.core @@ -45,94 +45,30 @@ - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-eclipse-plugin - - - org.codehaus.mojo - properties-maven-plugin - 1.0-alpha-2 - - - initialize - - read-project-properties - - - true - - ${basedir}/build.properties - ${basedir}/build.local.properties - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + - src/main/java + src/main/java - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - - org.codehaus.mojo - - - properties-maven-plugin - - - [1.0-alpha-2,) - - - - read-project-properties - - - - - - false - - - - - - - - - - + + + src/main/resources + true + + **/*.xml + **/*.properties + + + + From 08f358587b939f7e6ba15a3bf0ff9cedde2bb7de Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:08:39 -0300 Subject: [PATCH 27/92] pom.xml cleanup --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5c98a1..aef582fc 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,13 @@ 1.0 jar Java JWT - + org.sonatype.oss oss-parent 9 - + The MIT License @@ -20,7 +20,7 @@ repo - + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io From 4ef8a8f9c8956feb7a83292718c83f5106a13cb7 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:16:13 -0300 Subject: [PATCH 28/92] More pom.xml cleanup. --- pom.xml | 108 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/pom.xml b/pom.xml index aef582fc..e8738458 100644 --- a/pom.xml +++ b/pom.xml @@ -1,49 +1,69 @@ 4.0.0 + + + org.sonatype.oss + oss-parent + 9 + + com.auth0 - java-jwt-signer - 1.0 - jar + java-jwt + 0.3-SNAPSHOT + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io - - org.sonatype.oss - oss-parent - 9 - + + 1.6 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - repo - - + + + Alberto Pose + pose + + Developer + + + - Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. - http://www.jwt.io + + https://github.com/auth0/java-jwt + scm:git:git@github.com:auth0/java-jwt.git + scm:git:git@github.com:auth0/java-jwt.git + - - - com.fasterxml.jackson.core - jackson-databind - 2.0.0 - - - commons-codec - commons-codec - 1.4 - + + + com.fasterxml.jackson.core + jackson-databind + 2.0.0 + + + commons-codec + commons-codec + 1.4 + - - junit - junit - 4.11 - test - - + + junit + junit + 4.11 + test + + @@ -52,23 +72,11 @@ maven-compiler-plugin 3.1 - 1.6 - 1.6 + ${java.version} + ${java.version} + UTF-8 - - src/main/java - - - - src/main/resources - true - - **/*.xml - **/*.properties - - - From 4b661fe4a0410e58768084d181ca780919380b34 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:20:46 -0300 Subject: [PATCH 29/92] Changing tabs to spaces. --- src/test/java/com/auth0/jwt/TestHarness.java | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java index 9024dc50..8a314d7c 100644 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -10,24 +10,24 @@ */ public class TestHarness { - @Test - public void testHarness() throws Exception { - - final String secret = "This is a secret"; - final Algorithm algorithm = Algorithm.HS256; - - User user = new User(); - user.setUsername("jwt"); - user.setPassword("mypassword"); - - JwtProxy proxy = new JwtProxyImpl(); - proxy.setPayloadHandler(new BasicPayloadHandler()); - - ClaimSet claimSet = new ClaimSet(); - claimSet.setExp(24 * 60 * 60); // expire in 24 hours - String token = proxy.encode(algorithm, user, secret, claimSet); - - Object payload = proxy.decode(algorithm, token, secret); - assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); - } + @Test + public void testHarness() throws Exception { + + final String secret = "This is a secret"; + final Algorithm algorithm = Algorithm.HS256; + + User user = new User(); + user.setUsername("jwt"); + user.setPassword("mypassword"); + + JwtProxy proxy = new JwtProxyImpl(); + proxy.setPayloadHandler(new BasicPayloadHandler()); + + ClaimSet claimSet = new ClaimSet(); + claimSet.setExp(24 * 60 * 60); // expire in 24 hours + String token = proxy.encode(algorithm, user, secret, claimSet); + + Object payload = proxy.decode(algorithm, token, secret); + assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); + } } From b63fe8febf919cbebfe78b49e3c2610ab617befe Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:22:50 -0300 Subject: [PATCH 30/92] [maven-release-plugin] prepare release java-jwt-0.3 --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e8738458..74494e8f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 @@ -10,7 +9,7 @@ com.auth0 java-jwt - 0.3-SNAPSHOT + 0.3 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 6d1c80e58a30118585f36a53578e73b9cde8f3a3 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:22:58 -0300 Subject: [PATCH 31/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 74494e8f..9c2066ba 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.3 + 0.4-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 7a2dc2cdef5a06accd789e094af8ec03a0b3db35 Mon Sep 17 00:00:00 2001 From: Dani Date: Tue, 26 Aug 2014 13:52:23 +0200 Subject: [PATCH 32/92] Update README.md update on library version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14404e70..8b3b62ff 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.2 + 0.3 ``` From 98a3c556a36b672367417d7df689be02c70cf21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Fri, 5 Sep 2014 13:12:33 +0300 Subject: [PATCH 33/92] Support array-valued 'aud' Also don't break if user hasn't specified non-null audience. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 140291df..191f01f7 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,6 +1,5 @@ package com.auth0.jwt; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; @@ -125,11 +124,21 @@ void verifyIssuer(JsonNode jwtClaims) { } void verifyAudience(JsonNode jwtClaims) { - final String audienceFromToken = jwtClaims.has("aud") ? jwtClaims.get("aud").asText() : null; - - if (audienceFromToken != null && !audience.equals(audienceFromToken)) { - throw new IllegalStateException("jwt audience invalid"); + if (audience == null) + return; + JsonNode audNode = jwtClaims.get("aud"); + if (audNode == null) + return; + if (audNode.isArray()) { + for (JsonNode jsonNode : audNode) { + if (audience.equals(jsonNode.textValue())) + return; + } + } else if (audNode.isTextual()) { + if (audience.equals(audNode.textValue())) + return; } + throw new IllegalStateException("jwt audience invalid"); } String getAlgorithm(JsonNode jwtHeader) { From 62ad8b9b631ca67352cda4ccb7ad4a3eeded2f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Fri, 5 Sep 2014 13:31:37 +0300 Subject: [PATCH 34/92] Add tests for array-valued audience verification --- .../java/com/auth0/jwt/JWTVerifierTest.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index fa8b3205..3079fe8f 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1,14 +1,15 @@ package com.auth0.jwt; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.codec.binary.Base64; import org.junit.Test; import java.security.SignatureException; -import java.util.Collections; -import java.util.Map; import static org.junit.Assert.assertEquals; @@ -137,6 +138,26 @@ public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { .verifyAudience(JsonNodeFactory.instance.objectNode()); } + @Test + public void shouldVerifyNullAudience() throws Exception { + new JWTVerifier("such secret") + .verifyAudience(createSingletonJSONNode("aud", "wow")); + } + + @Test + public void shouldVerifyArrayAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(createSingletonJSONNode("aud", + new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailArrayAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(createSingletonJSONNode("aud", + new ObjectMapper().readValue("[ \"foo\" ]", ArrayNode.class))); + } + @Test public void decodeAndParse() throws Exception { final Base64 encoder = new Base64(true); @@ -156,4 +177,10 @@ public static JsonNode createSingletonJSONNode(String key, String value) { jsonNodes.put(key, value); return jsonNodes; } + + public static JsonNode createSingletonJSONNode(String key, JsonNode value) { + final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); + jsonNodes.put(key, value); + return jsonNodes; + } } From df150445fe6308937528de5e2d19bcdd42f52a51 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 5 Sep 2014 09:18:12 -0300 Subject: [PATCH 35/92] [maven-release-plugin] prepare release java-jwt-0.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9c2066ba..70f7f267 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.4-SNAPSHOT + 0.4 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From f123f3ed8e75eb72732936c317de59d0722bdc1a Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 5 Sep 2014 09:18:18 -0300 Subject: [PATCH 36/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 70f7f267..57f38505 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.4 + 0.5-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 0bd0679bfc8b2dd26ecbfe1891ef29a4c476dec0 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 8 Sep 2014 13:40:38 -0300 Subject: [PATCH 37/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b3b62ff..d2f744b2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.3 + 0.4 ``` From d2230d69bc8b77e093cdd954f908e15fc802b657 Mon Sep 17 00:00:00 2001 From: Jan Stamer Date: Tue, 30 Sep 2014 09:47:26 +0200 Subject: [PATCH 38/92] Repackage maven dependencies to avoid conflicts with Jackson version. --- .gitignore | 3 +++ pom.xml | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.gitignore b/.gitignore index 2e9d0dd1..0e60b940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Maven Shaded Jar Artifact +dependency-reduced-pom.xml + node_modules # Ignore Eclipse stuff diff --git a/pom.xml b/pom.xml index 57f38505..5f981d48 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 1.6 + com.auth0.jwt.internal @@ -76,6 +77,33 @@ UTF-8 + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + false + true + + + com.fasterxml.jackson + ${repackage.base}.com.fasterxml.jackson + + + org.apache.commons.codec + ${repackage.base}.org.apache.commons.codec + + + + + + From e43647960804f3dd64620eb6506eb4a8f686a7c7 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:01:02 -0300 Subject: [PATCH 39/92] [maven-release-plugin] prepare release java-jwt-0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f981d48..7861f28e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.5-SNAPSHOT + 0.5 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 84f52c09bf1c0b312f91fde13eb8b752e8f408df Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:01:16 -0300 Subject: [PATCH 40/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7861f28e..51fcc5f6 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.5 + 0.6-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 490d465415265aa88c91a9a251ff34d5a61a61f9 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:08:06 -0300 Subject: [PATCH 41/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2f744b2..3275f75c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.4 + 0.5 ``` From 03f04b51b5bdaf25d7d6666ec240fa7a063d1dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Tue, 16 Sep 2014 19:25:02 +0300 Subject: [PATCH 42/92] Complete change of encode() API and implementation --- .gitignore | 1 + src/main/java/com/auth0/jwt/ClaimSet.java | 14 - src/main/java/com/auth0/jwt/JWTSigner.java | 333 ++++++++++++++++++ src/main/java/com/auth0/jwt/JwtProxy.java | 8 - src/main/java/com/auth0/jwt/JwtSigner.java | 141 -------- .../java/com/auth0/jwt/PayloadHandler.java | 10 - .../auth0/jwt/impl/BasicPayloadHandler.java | 18 - .../java/com/auth0/jwt/impl/JwtProxyImpl.java | 55 --- .../java/com/auth0/jwt/JWTSignerTest.java | 185 ++++++++++ .../java/com/auth0/jwt/RoundtripTest.java | 142 ++++++++ src/test/java/com/auth0/jwt/TestHarness.java | 33 -- 11 files changed, 661 insertions(+), 279 deletions(-) delete mode 100644 src/main/java/com/auth0/jwt/ClaimSet.java create mode 100644 src/main/java/com/auth0/jwt/JWTSigner.java delete mode 100644 src/main/java/com/auth0/jwt/JwtProxy.java delete mode 100644 src/main/java/com/auth0/jwt/JwtSigner.java delete mode 100644 src/main/java/com/auth0/jwt/PayloadHandler.java delete mode 100644 src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java delete mode 100644 src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java create mode 100644 src/test/java/com/auth0/jwt/JWTSignerTest.java create mode 100644 src/test/java/com/auth0/jwt/RoundtripTest.java delete mode 100644 src/test/java/com/auth0/jwt/TestHarness.java diff --git a/.gitignore b/.gitignore index 2e9d0dd1..98b57f51 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules # Ignore Eclipse stuff .project .settings +.classpath # Ignore Java and IntelliJ IDEA stuff .idea diff --git a/src/main/java/com/auth0/jwt/ClaimSet.java b/src/main/java/com/auth0/jwt/ClaimSet.java deleted file mode 100644 index 8c401684..00000000 --- a/src/main/java/com/auth0/jwt/ClaimSet.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.auth0.jwt; - -public class ClaimSet { - - private int exp; - - public int getExp() { - return exp; - } - - public void setExp(int exp) { - this.exp = (int)(System.currentTimeMillis() / 1000L) + exp; - } -} diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java new file mode 100644 index 00000000..55084a12 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -0,0 +1,333 @@ +package com.auth0.jwt; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JwtSigner implementation based on the Ruby implementation from http://jwt.io + * No support for RSA encryption at present + */ +public class JWTSigner { + private final String secret; + + public JWTSigner(String secret) { + this.secret = secret; + } + + /** + * Generate a JSON Web Token. + * using the default algorithm HMAC SHA-256 ("HS256") + * and no claims automatically set. + * + * @param claims A map of the JWT claims that form the payload. Registered claims + * must be of appropriate Java datatype as following: + *

+ * All claims with a null value are left out the JWT. + * Any claims set automatically as specified in + * the "options" parameter override claims in this map. + * + * @param secret Key to use in signing. Used as-is without Base64 encoding. + * + * @param options Allow choosing the signing algorithm, and automatic setting of some registered claims. + */ + public String sign(Map claims, Options options) { + Algorithm algorithm = Algorithm.HS256; + if (options != null && options.algorithm != null) + algorithm = options.algorithm; + + List segments = new ArrayList(); + try { + segments.add(encodedHeader(algorithm)); + segments.add(encodedPayload(claims, options)); + segments.add(encodedSignature(join(segments, "."), algorithm)); + } catch (Exception e) { + throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e); + } + + return join(segments, "."); + } + + /** + * Generate a JSON Web Token using the default algorithm HMAC SHA-256 ("HS256") + * and no claims automatically set. + * + * @param secret Key to use in signing. Used as-is without Base64 encoding. + * + * For details, see the two parameter variant of this method. + */ + public String sign(Map claims) { + return sign(claims, null); + } + + /** + * Generate the header part of a JSON web token. + */ + private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingException { + if (algorithm == null) { // default the algorithm if not specified + algorithm = Algorithm.HS256; + } + + // create the header + ObjectNode header = JsonNodeFactory.instance.objectNode(); + header.put("type", "JWT"); + header.put("alg", algorithm.name()); + + return base64UrlEncode(header.toString().getBytes("UTF-8")); + } + + /** + * Generate the JSON web token payload string from the claims. + * @param options + */ + private String encodedPayload(Map _claims, Options options) throws Exception { + Map claims = new HashMap(_claims); + enforceStringOrURI(claims, "iss"); + enforceStringOrURI(claims, "sub"); + enforceStringOrURICollection(claims, "aud"); + enforceIntDate(claims, "exp"); + enforceIntDate(claims, "nbf"); + enforceIntDate(claims, "iat"); + enforceString(claims, "jti"); + + if (options != null) + processPayloadOptions(claims, options); + + String payload = new ObjectMapper().writeValueAsString(claims); + return base64UrlEncode(payload.getBytes("UTF-8")); + } + + private void processPayloadOptions(Map claims, Options options) { + long now = System.currentTimeMillis() / 1000l; + if (options.expirySeconds != null) + claims.put("exp", now + options.expirySeconds); + if (options.notValidBeforeLeeway != null) + claims.put("nbf", now - options.notValidBeforeLeeway); + if (options.isIssuedAt()) + claims.put("iat", now); + if (options.isJwtId()) + claims.put("jti", UUID.randomUUID().toString()); + } + + private void enforceIntDate(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + if (!(value instanceof Number)) { + throw new RuntimeException(String.format("Claim '%s' is invalid: must be an instance of Number", claimName)); + } + long longValue = ((Number) value).longValue(); + if (longValue < 0) + throw new RuntimeException(String.format("Claim '%s' is invalid: must be non-negative", claimName)); + claims.put(claimName, longValue); + } + + private void enforceStringOrURICollection(Map claims, String claimName) { + Object values = handleNullValue(claims, claimName); + if (values == null) + return; + if (values instanceof Collection) { + @SuppressWarnings({ "unchecked" }) + Iterator iterator = ((Collection) values).iterator(); + while (iterator.hasNext()) { + Object value = iterator.next(); + String error = checkStringOrURI(value); + if (error != null) + throw new RuntimeException(String.format("Claim 'aud' element is invalid: %s", error)); + } + } else { + enforceStringOrURI(claims, "aud"); + } + } + + private void enforceStringOrURI(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + String error = checkStringOrURI(value); + if (error != null) + throw new RuntimeException(String.format("Claim '%s' is invalid: %s", claimName, error)); + } + + private void enforceString(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + if (!(value instanceof String)) + throw new RuntimeException(String.format("Claim '%s' is invalid: not a string", claimName)); + } + + private Object handleNullValue(Map claims, String claimName) { + if (! claims.containsKey(claimName)) + return null; + Object value = claims.get(claimName); + if (value == null) { + claims.remove(claimName); + return null; + } + return value; + } + + private String checkStringOrURI(Object value) { + if (!(value instanceof String)) + return "not a string"; + String stringOrUri = (String) value; + if (!stringOrUri.contains(":")) + return null; + try { + new URI(stringOrUri); + } catch (URISyntaxException e) { + return "not a valid URI"; + } + return null; + } + + /** + * Sign the header and payload + */ + private String encodedSignature(String signingInput, Algorithm algorithm) throws Exception { + byte[] signature = sign(algorithm, signingInput, secret); + return base64UrlEncode(signature); + } + + /** + * Safe URL encode a byte array to a String + */ + private String base64UrlEncode(byte[] str) { + return new String(Base64.encodeBase64URLSafe(str)); + } + + /** + * Switch the signing algorithm based on input, RSA not supported + */ + private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + switch (algorithm) { + case HS256: + case HS384: + case HS512: + return signHmac(algorithm, msg, secret); + case RS256: + case RS384: + case RS512: + default: + throw new OperationNotSupportedException("Unsupported signing method"); + } + } + + /** + * Sign an input string using HMAC and return the encrypted bytes + */ + private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + Mac mac = Mac.getInstance(algorithm.getValue()); + mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + return mac.doFinal(msg.getBytes()); + } + + private String join(List input, String on) { + int size = input.size(); + int count = 1; + StringBuilder joined = new StringBuilder(); + for (String string : input) { + joined.append(string); + if (count < size) { + joined.append(on); + } + count++; + } + + return joined.toString(); + } + + /** + * An option object for JWT signing operation. Allow choosing the algorithm, and/or specifying + * claims to be automatically set. + */ + public static class Options { + private Algorithm algorithm; + private Integer expirySeconds; + private Integer notValidBeforeLeeway; + private boolean issuedAt; + private boolean jwtId; + + public Algorithm getAlgorithm() { + return algorithm; + } + /** + * Algorithm to sign JWT with. Default is HS256. + */ + public Options setAlgorithm(Algorithm algorithm) { + this.algorithm = algorithm; + return this; + } + + + public Integer getExpirySeconds() { + return expirySeconds; + } + /** + * Set JWT claim "exp" to current timestamp plus this value. + * Overrides content of claims in sign(). + */ + public Options setExpirySeconds(Integer expirySeconds) { + this.expirySeconds = expirySeconds; + return this; + } + + public Integer getNotValidBeforeLeeway() { + return notValidBeforeLeeway; + } + /** + * Set JWT claim "nbf" to current timestamp minus this value. + * Overrides content of claims in sign(). + */ + public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { + this.notValidBeforeLeeway = notValidBeforeLeeway; + return this; + } + + public boolean isIssuedAt() { + return issuedAt; + } + /** + * Set JWT claim "iat" to current timestamp. Defaults to false. + * Overrides content of claims in sign(). + */ + public Options setIssuedAt(boolean issuedAt) { + this.issuedAt = issuedAt; + return this; + } + + public boolean isJwtId() { + return jwtId; + } + /** + * Set JWT claim "jti" to a pseudo random unique value (type 4 UUID). Defaults to false. + * Overrides content of claims in sign(). + */ + public Options setJwtId(boolean jwtId) { + this.jwtId = jwtId; + return this; + } + } +} diff --git a/src/main/java/com/auth0/jwt/JwtProxy.java b/src/main/java/com/auth0/jwt/JwtProxy.java deleted file mode 100644 index da7c7f3d..00000000 --- a/src/main/java/com/auth0/jwt/JwtProxy.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.auth0.jwt; - -public interface JwtProxy { - - void setPayloadHandler(PayloadHandler payloadHandler); - String encode(Algorithm algorithm, Object obj, String secret, ClaimSet claimSet) throws Exception; - Object decode(Algorithm algorithm, String token, String secret) throws Exception; -} diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java deleted file mode 100644 index 5c1a9694..00000000 --- a/src/main/java/com/auth0/jwt/JwtSigner.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.auth0.jwt; - -import java.util.ArrayList; -import java.util.List; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.naming.OperationNotSupportedException; - -import org.apache.commons.codec.binary.Base64; - -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * JwtSigner implementation based on the Ruby implementation from http://jwt.io - * No support for RSA encryption at present - */ -public class JwtSigner { - - /** - * Generate a JSON web token based on a payload, secret key and claim set - */ - public String encode(Algorithm algorithm, String payload, String payloadId, String key, - ClaimSet claimSet) throws Exception { - - List segments = new ArrayList(); - - segments.add(encodedHeader(algorithm)); - segments.add(encodedPayload(payload, payloadId, claimSet)); - segments.add(encodedSignature(join(segments, "."), key, algorithm)); - - return join(segments, "."); - } - - /** - * Generate the header part of a JSON web token - */ - private String encodedHeader(Algorithm algorithm) - throws Exception { - - if (algorithm == null) { // default the algorithm if not specified - algorithm = Algorithm.HS256; - } - - // create the header - ObjectNode header = JsonNodeFactory.instance.objectNode(); - header.put("type", "JWT"); - header.put("alg", algorithm.name()); - - return base64UrlEncode(header.toString().getBytes()); - } - - /** - * Generate the JSON web token payload, merging it with the claim set - */ - private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { - - ObjectNode localClaimSet = JsonNodeFactory.instance.objectNode(); - ObjectNode localPayload = JsonNodeFactory.instance.objectNode(); - - localPayload.put(payloadId, payload); - - if(claimSet != null) { - if(claimSet.getExp() > 0) { - localClaimSet.put("exp", claimSet.getExp()); - } - localPayload.putAll(localClaimSet); - } - - return base64UrlEncode(localPayload.toString().getBytes()); - } - - /** - * Sign the header and payload - */ - private String encodedSignature(String signingInput, String key, - Algorithm algorithm) throws Exception { - - byte[] signature = sign(algorithm, signingInput, key); - return base64UrlEncode(signature); - } - - /** - * Safe URL encode a byte array to a String - */ - private String base64UrlEncode(byte[] str) throws Exception { - - return new String(Base64.encodeBase64URLSafe(str)); - } - - /** - * Switch the signing algorithm based on input, RSA not supported - */ - private byte[] sign(Algorithm algorithm, String msg, String key) - throws Exception { - - switch (algorithm) { - case HS256: - case HS384: - case HS512: - return signHmac(algorithm, msg, key); - case RS256: - case RS384: - case RS512: - default: - throw new OperationNotSupportedException( - "Unsupported signing method"); - } - } - - /** - * Sign an input string using HMAC and return the encrypted bytes - */ - private byte[] signHmac(Algorithm algorithm, String msg, String key) - throws Exception { - - Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(key.getBytes(), algorithm.getValue())); - return mac.doFinal(msg.getBytes()); - } - - /** - * Mimick the ruby array.join function - */ - private String join(List input, String on) { - - int size = input.size(); - int count = 1; - StringBuilder joined = new StringBuilder(); - for (String string : input) { - joined.append(string); - if (count < size) { - joined.append(on); - } - count++; - } - - return joined.toString(); - } -} diff --git a/src/main/java/com/auth0/jwt/PayloadHandler.java b/src/main/java/com/auth0/jwt/PayloadHandler.java deleted file mode 100644 index 24fd8eca..00000000 --- a/src/main/java/com/auth0/jwt/PayloadHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.auth0.jwt; - -/** - * Abstraction to allow custom payload handling e.g. in the event the payload needs to be encrypted - */ -public interface PayloadHandler { - - String encoding(Object payload) throws Exception; - Object decoding(String payload) throws Exception; -} diff --git a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java deleted file mode 100644 index e198a28a..00000000 --- a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.auth0.jwt.impl; - -import com.auth0.jwt.PayloadHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Basic implementation of a payload handler which serializes the payload to a String, and echoes it for deserialization - */ -public final class BasicPayloadHandler implements PayloadHandler { - - public String encoding(Object payload) throws Exception { - return new ObjectMapper().writeValueAsString(payload); - } - - public Object decoding(String payload) throws Exception { - return payload; - } -} diff --git a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java deleted file mode 100644 index ba0e7a6b..00000000 --- a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.auth0.jwt.impl; - -import java.util.Map; - -import org.apache.commons.codec.binary.Base64; - -import com.auth0.jwt.Algorithm; -import com.auth0.jwt.ClaimSet; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.JwtProxy; -import com.auth0.jwt.JwtSigner; -import com.auth0.jwt.PayloadHandler; - -/** - * JwtProxy implementation - */ -public class JwtProxyImpl implements JwtProxy { - - // the payload identifier in the JSON object - private static final String PAYLOAD_ID = "payload"; - private PayloadHandler payloadHandler; - - public void setPayloadHandler(PayloadHandler payloadHandler) { - this.payloadHandler = payloadHandler; - } - - public PayloadHandler getPayloadHandler() { - return payloadHandler; - } - - /** - * Create a JSON web token by serializing a java object - */ - public String encode(Algorithm algorithm, Object obj, String secret, - ClaimSet claimSet) throws Exception { - - JwtSigner jwtSigner = new JwtSigner(); - String payload = getPayloadHandler().encoding(obj); - - return jwtSigner.encode(algorithm, payload, PAYLOAD_ID, secret, claimSet); - } - - /** - * Verify a JSON web token and return the object serialized in the JSON payload - */ - public Object decode(Algorithm algorithm, String token, String secret) - throws Exception { - - JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(secret.getBytes())); - Map verify = jwtVerifier.verify(token); - String payload = (String) verify.get(PAYLOAD_ID); - - return getPayloadHandler().decoding(payload); - } -} diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest.java b/src/test/java/com/auth0/jwt/JWTSignerTest.java new file mode 100644 index 00000000..9032d6e6 --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTSignerTest.java @@ -0,0 +1,185 @@ +package com.auth0.jwt; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; + +public class JWTSignerTest { + private static JWTSigner signer = new JWTSigner("my secret"); + + @Test + public void shouldSignEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldSignEmptyTwoParams() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldSignStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJmb28ifQ.7VNaEEPhOiEXfEgPrxkFFhQCAxl9X3F20sq9KVaVtJM", token); + } + + @Test + public void shouldSignStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", "http://foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJodHRwOi8vZm9vIn0.GYhCLgXYbAXp2Lr8T2yif7ylBVK1XZFkO8hEBa8WP8U", token); + } + + @Test + public void shouldSignStringOrURI3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", ""); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOiIifQ.qobL4k5su7O7ssfCr7drTScIhWjheIc9uxipkR9MC0A", token); + } + + @Test + public void shouldSignStringOrURICollection() throws Exception { + HashMap claims = new HashMap(); + LinkedList aud = new LinkedList(); + aud.add("xyz"); + aud.add("ftp://foo"); + claims.put("aud", aud); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.xL_8PVO_8isoFSud1Nlqi8rA3jvdD5zALN3tjcQ0vbk", token); + } + + @Test + public void shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjEyM30.1pI_TNQDCsKc3IEVX_2fcAKJmJZ8j3hhOfAvAdqKE0s", token); + } + + @Test + public void shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYmYiOjB9.uxwAmWxxPZwGRgfiXOHGxrXmxgay6tv93Pyiya3O5dE", token); + } + + @Test + public void shouldSignIntDate3() throws Exception { + HashMap claims = new HashMap(); + claims.put("iat", Long.MAX_VALUE); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.nsfMBVmmDR0u1tVN54UzHDZL2wylDA50YjzN2WxZEsU", token); + } + + @Test + public void shouldSignString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiJmb28ifQ.6X8nx7sLNxdc4mYNL__gd0ab-m8QfheVHT2Y_2DQJMU", token); + } + + @Test + public void shouldSignNullEqualsMissing() throws Exception { + HashMap claims = new HashMap(); + for (String claimName : Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")) { + claims.put(claimName, null); + } + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", ":"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection1() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection2() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(0)); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(":")); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", -1); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", "100"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", 100); + signer.sign(claims); + } + + @Test + public void shouldOptionsNone() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, new JWTSigner.Options()); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldOptionsAll() throws Exception { + HashMap claims = new HashMap(); + signer.sign(claims, new JWTSigner.Options() + .setExpirySeconds(1000).setNotValidBeforeLeeway(5) + .setIssuedAt(true).setJwtId(true)); + } + + @Test + public void shouldOptionsAlgorithm() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.e30.gH4cjvHOMA2QcZjwSqO-VZ4tyah8hDMVqUGAOth7vBWweOIzCwohpOlpLoRCKeDD3PyMqE1gwHqGuWDk2VuYmQ", token); + } + +} diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java new file mode 100644 index 00000000..53330e77 --- /dev/null +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -0,0 +1,142 @@ +package com.auth0.jwt; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Test; + +/** + * Test things that are difficult using signer or verifier alone. In particular, setting + * claims via Options produces output dependent on current time. + * + */ +public class RoundtripTest { + private static final String SECRET; + private static final String SECRET_BASE64; + static { + try { + SECRET = "my secret"; + SECRET_BASE64 = Base64.encodeBase64String(SECRET.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + private static JWTSigner signer = new JWTSigner(SECRET); + private static JWTVerifier verifier = new JWTVerifier(SECRET_BASE64); + + /* + * Roundtrip of different datatypes. + */ + @Test + public void shouldEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldString() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", "bar"); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldShort() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", (short) -10); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + Number fooValue = (Number) decoded.get("foo"); + decoded.put("foo", fooValue.shortValue()); + assertEquals(claims, decoded); + } + + @Test + public void shouldLong() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", Long.MAX_VALUE); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldObject() throws Exception { + HashMap claims = new HashMap(); + User user = new User(); + user.setUsername("foo"); + user.setPassword("bar"); + claims.put("user", user); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + HashMap expectedUser = new HashMap(); + expectedUser.put("username", "foo"); + expectedUser.put("password", "bar"); + HashMap expected = new HashMap(); + expected.put("user", expectedUser); + assertEquals(expected, decoded); + } + + @Test + public void shouldBoolean() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", true); + claims.put("bar", false); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + /* + * Setting claims via Options + */ + + @Test + public void shouldOptionsIat() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setIssuedAt(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); + long iat = ((Number) decoded.get("iat")).longValue(); + assertTrue(iat >= System.currentTimeMillis() / 1000l); + assertTrue(iat < System.currentTimeMillis() / 1000l + 10); + } + + @Test + public void shouldOptionsTimestamps() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options() + .setExpirySeconds(50).setNotValidBeforeLeeway(10).setIssuedAt(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 3); + long iat = ((Number) decoded.get("iat")).longValue(); + long exp = ((Number) decoded.get("exp")).longValue(); + long nbf = ((Number) decoded.get("nbf")).longValue(); + assertEquals(exp, iat + 50); + assertEquals(nbf, iat - 10); + } + + @Test + public void shouldOptionsJti() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setJwtId(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); + assertEquals(((String) decoded.get("jti")).length(), 36); + } +} + + + diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java deleted file mode 100644 index 8a314d7c..00000000 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.auth0.jwt; - -import static org.junit.Assert.*; -import com.auth0.jwt.impl.BasicPayloadHandler; -import com.auth0.jwt.impl.JwtProxyImpl; -import org.junit.Test; - -/** - * Test harness for JwtProxy - */ -public class TestHarness { - - @Test - public void testHarness() throws Exception { - - final String secret = "This is a secret"; - final Algorithm algorithm = Algorithm.HS256; - - User user = new User(); - user.setUsername("jwt"); - user.setPassword("mypassword"); - - JwtProxy proxy = new JwtProxyImpl(); - proxy.setPayloadHandler(new BasicPayloadHandler()); - - ClaimSet claimSet = new ClaimSet(); - claimSet.setExp(24 * 60 * 60); // expire in 24 hours - String token = proxy.encode(algorithm, user, secret, claimSet); - - Object payload = proxy.decode(algorithm, token, secret); - assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); - } -} From 7c8cb413a9232d8bad3dd3143cadc1ea630b7a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 23 Oct 2014 10:03:13 -0300 Subject: [PATCH 43/92] Grammar fixes for README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3275f75c..88fce9fc 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ Yes, here you are: ### Credits -Most of the code have been written by Luis Faja . We just wrapped it on a nicer interface and published it to maven. We'll be adding support for signing and other algorithms in the future. +Most of the code have been written by Luis Faja . We just wrapped it in a nicer interface and published it to Maven Central. We'll be adding support for signing and other algorithms in the future. ### FAQ #### Why another JSON Web Token implementation for Java? -We think that current JWT implementations are either too complex or not enough tested. We want something simple with the right number of abstractions. +We think that current JWT implementations are either too complex or not tested enough. We want something simple with the right number of abstractions. ## License From c3a8f855a944ed447cc0b13ced564b5c2f7947fe Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 16:40:04 -0300 Subject: [PATCH 44/92] [maven-release-plugin] prepare release java-jwt-1.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51fcc5f6..742f7e6e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.6-SNAPSHOT + 1.0.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From eb5da3eee2f5a1c02308b2b08dd787ac936690c2 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 16:40:09 -0300 Subject: [PATCH 45/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 742f7e6e..98269714 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 1.0.0 + 1.0.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 2f1766b701c26f20f62bab3801f40e89005bf5b7 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 17:55:02 -0300 Subject: [PATCH 46/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88fce9fc..36bcf298 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.5 + 1.0.0 ``` From c27801b97f39075054924288b64687c18faf0907 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 24 Nov 2014 10:04:41 -0300 Subject: [PATCH 47/92] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 36bcf298..940c35c0 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ Most of the code have been written by Luis Faja Date: Tue, 2 Dec 2014 10:23:07 +0100 Subject: [PATCH 48/92] Use explicit JWTVerifyException for claims related verifying Close #11 --- .../com/auth0/jwt/JWTAudienceException.java | 31 +++++++++++++++++++ .../com/auth0/jwt/JWTExpiredException.java | 18 +++++++++++ .../com/auth0/jwt/JWTIssuerException.java | 18 +++++++++++ src/main/java/com/auth0/jwt/JWTVerifier.java | 18 ++++++----- .../com/auth0/jwt/JWTVerifyException.java | 10 ++++++ .../java/com/auth0/jwt/JWTVerifierTest.java | 8 ++--- 6 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/auth0/jwt/JWTAudienceException.java create mode 100644 src/main/java/com/auth0/jwt/JWTExpiredException.java create mode 100644 src/main/java/com/auth0/jwt/JWTIssuerException.java create mode 100644 src/main/java/com/auth0/jwt/JWTVerifyException.java diff --git a/src/main/java/com/auth0/jwt/JWTAudienceException.java b/src/main/java/com/auth0/jwt/JWTAudienceException.java new file mode 100644 index 00000000..6c3da263 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTAudienceException.java @@ -0,0 +1,31 @@ +package com.auth0.jwt; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.List; + +public class JWTAudienceException extends JWTVerifyException { + private JsonNode audienceNode; + + public JWTAudienceException(JsonNode audienceNode) { + this.audienceNode = audienceNode; + } + + public JWTAudienceException(String message, JsonNode audienceNode) { + super(message); + this.audienceNode = audienceNode; + } + + public List getAudience() { + ArrayList audience = new ArrayList(); + if (audienceNode.isArray()) { + for (JsonNode jsonNode : audienceNode) { + audience.add(jsonNode.textValue()); + } + } else if (audienceNode.isTextual()) { + audience.add(audienceNode.textValue()); + } + return audience; + } +} diff --git a/src/main/java/com/auth0/jwt/JWTExpiredException.java b/src/main/java/com/auth0/jwt/JWTExpiredException.java new file mode 100644 index 00000000..50178aa8 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTExpiredException.java @@ -0,0 +1,18 @@ +package com.auth0.jwt; + +public class JWTExpiredException extends JWTVerifyException { + private long expiration; + + public JWTExpiredException(long expiration) { + this.expiration = expiration; + } + + public JWTExpiredException(String message, long expiration) { + super(message); + this.expiration = expiration; + } + + public long getExpiration() { + return expiration; + }; +} diff --git a/src/main/java/com/auth0/jwt/JWTIssuerException.java b/src/main/java/com/auth0/jwt/JWTIssuerException.java new file mode 100644 index 00000000..4dc1cf16 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTIssuerException.java @@ -0,0 +1,18 @@ +package com.auth0.jwt; + +public class JWTIssuerException extends JWTVerifyException { + private final String issuer; + + public JWTIssuerException(String issuer) { + this.issuer = issuer; + } + + public JWTIssuerException(String message, String issuer) { + super(message); + this.issuer = issuer; + } + + public String getIssuer() { + return issuer; + } +} diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 191f01f7..c1a70f0f 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -10,6 +10,7 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -62,11 +63,12 @@ public JWTVerifier(String secret) { * * @param token token to verify * @throws SignatureException when signature is invalid - * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid + * @throws JWTVerifyException when expiration, issuer or audience are invalid + * @throws IllegalStateException when token's structure is invalid */ public Map verify(String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, - IOException, SignatureException { + IOException, SignatureException, JWTVerifyException { if (token == null || "".equals(token)) { throw new IllegalStateException("token not set"); } @@ -107,23 +109,23 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx } } - void verifyExpiration(JsonNode jwtClaims) { + void verifyExpiration(JsonNode jwtClaims) throws JWTExpiredException { final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { - throw new IllegalStateException("jwt expired"); + throw new JWTExpiredException("jwt expired", expiration); } } - void verifyIssuer(JsonNode jwtClaims) { + void verifyIssuer(JsonNode jwtClaims) throws JWTIssuerException { final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { - throw new IllegalStateException("jwt issuer invalid"); + throw new JWTIssuerException("jwt issuer invalid", issuerFromToken); } } - void verifyAudience(JsonNode jwtClaims) { + void verifyAudience(JsonNode jwtClaims) throws JWTAudienceException { if (audience == null) return; JsonNode audNode = jwtClaims.get("aud"); @@ -138,7 +140,7 @@ void verifyAudience(JsonNode jwtClaims) { if (audience.equals(audNode.textValue())) return; } - throw new IllegalStateException("jwt audience invalid"); + throw new JWTAudienceException("jwt audience invalid", audNode); } String getAlgorithm(JsonNode jwtHeader) { diff --git a/src/main/java/com/auth0/jwt/JWTVerifyException.java b/src/main/java/com/auth0/jwt/JWTVerifyException.java new file mode 100644 index 00000000..08bc583b --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTVerifyException.java @@ -0,0 +1,10 @@ +package com.auth0.jwt; + +public class JWTVerifyException extends Exception { + public JWTVerifyException() { + } + + public JWTVerifyException(String message) { + super(message); + } +} diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 3079fe8f..b5fa998d 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -90,7 +90,7 @@ public void shouldVerifySignature() throws Exception { .verifySignature(jws.split("\\."), "HmacSHA256"); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTExpiredException.class) public void shouldFailWhenExpired1SecondAgo() throws Exception { new JWTVerifier("such secret").verifyExpiration( createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); @@ -108,7 +108,7 @@ public void shouldVerifyIssuer() throws Exception { .verifyIssuer(createSingletonJSONNode("iss", "very issuer")); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTIssuerException.class) public void shouldFailIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") .verifyIssuer(createSingletonJSONNode("iss", "wow")); @@ -126,7 +126,7 @@ public void shouldVerifyAudience() throws Exception { .verifyAudience(createSingletonJSONNode("aud", "amaze audience")); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTAudienceException.class) public void shouldFailAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") .verifyAudience(createSingletonJSONNode("aud", "wow")); @@ -151,7 +151,7 @@ public void shouldVerifyArrayAudience() throws Exception { new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTAudienceException.class) public void shouldFailArrayAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") .verifyAudience(createSingletonJSONNode("aud", From 3d665f366d59ccea82a4c5b3f2d5fe98e01823f9 Mon Sep 17 00:00:00 2001 From: OCW-DUO Date: Mon, 15 Dec 2014 09:25:32 +0100 Subject: [PATCH 49/92] Made project compatible with jdk 1.5 For legacy purposes, the java-jwt project can now be used on 1.5+ --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 98269714..dab8d4b9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ http://www.jwt.io - 1.6 + 1.5 com.auth0.jwt.internal @@ -49,7 +49,7 @@ com.fasterxml.jackson.core jackson-databind - 2.0.0 + 2.0.1 commons-codec From ae8abc28566c3464111b3325360ac687671a1209 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 18:37:55 -0300 Subject: [PATCH 50/92] Fixed header. Changed type for typ. Fixes #17 --- src/main/java/com/auth0/jwt/JWTSigner.java | 2 +- .../java/com/auth0/jwt/JWTSignerTest.java | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 55084a12..84dfc46d 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -91,7 +91,7 @@ private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingExce // create the header ObjectNode header = JsonNodeFactory.instance.objectNode(); - header.put("type", "JWT"); + header.put("typ", "JWT"); header.put("alg", algorithm.name()); return base64UrlEncode(header.toString().getBytes("UTF-8")); diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest.java b/src/test/java/com/auth0/jwt/JWTSignerTest.java index 9032d6e6..a63f52d3 100644 --- a/src/test/java/com/auth0/jwt/JWTSignerTest.java +++ b/src/test/java/com/auth0/jwt/JWTSignerTest.java @@ -16,14 +16,14 @@ public class JWTSignerTest { public void shouldSignEmpty() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test public void shouldSignEmptyTwoParams() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test @@ -31,7 +31,7 @@ public void shouldSignStringOrURI1() throws Exception { HashMap claims = new HashMap(); claims.put("iss", "foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJmb28ifQ.7VNaEEPhOiEXfEgPrxkFFhQCAxl9X3F20sq9KVaVtJM", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); } @Test @@ -39,7 +39,7 @@ public void shouldSignStringOrURI2() throws Exception { HashMap claims = new HashMap(); claims.put("sub", "http://foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJodHRwOi8vZm9vIn0.GYhCLgXYbAXp2Lr8T2yif7ylBVK1XZFkO8hEBa8WP8U", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); } @Test @@ -47,7 +47,7 @@ public void shouldSignStringOrURI3() throws Exception { HashMap claims = new HashMap(); claims.put("aud", ""); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOiIifQ.qobL4k5su7O7ssfCr7drTScIhWjheIc9uxipkR9MC0A", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); } @Test @@ -58,7 +58,7 @@ public void shouldSignStringOrURICollection() throws Exception { aud.add("ftp://foo"); claims.put("aud", aud); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.xL_8PVO_8isoFSud1Nlqi8rA3jvdD5zALN3tjcQ0vbk", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); } @Test @@ -66,7 +66,7 @@ public void shouldSignIntDate1() throws Exception { HashMap claims = new HashMap(); claims.put("exp", 123); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjEyM30.1pI_TNQDCsKc3IEVX_2fcAKJmJZ8j3hhOfAvAdqKE0s", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); } @Test @@ -74,7 +74,7 @@ public void shouldSignIntDate2() throws Exception { HashMap claims = new HashMap(); claims.put("nbf", 0); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYmYiOjB9.uxwAmWxxPZwGRgfiXOHGxrXmxgay6tv93Pyiya3O5dE", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); } @Test @@ -82,7 +82,7 @@ public void shouldSignIntDate3() throws Exception { HashMap claims = new HashMap(); claims.put("iat", Long.MAX_VALUE); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.nsfMBVmmDR0u1tVN54UzHDZL2wylDA50YjzN2WxZEsU", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); } @Test @@ -90,7 +90,7 @@ public void shouldSignString() throws Exception { HashMap claims = new HashMap(); claims.put("jti", "foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiJmb28ifQ.6X8nx7sLNxdc4mYNL__gd0ab-m8QfheVHT2Y_2DQJMU", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); } @Test @@ -100,7 +100,7 @@ public void shouldSignNullEqualsMissing() throws Exception { claims.put(claimName, null); } String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test(expected = Exception.class) @@ -163,7 +163,7 @@ public void shouldFailExpectString() throws Exception { public void shouldOptionsNone() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims, new JWTSigner.Options()); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test @@ -179,7 +179,7 @@ public void shouldOptionsAlgorithm() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims, new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.e30.gH4cjvHOMA2QcZjwSqO-VZ4tyah8hDMVqUGAOth7vBWweOIzCwohpOlpLoRCKeDD3PyMqE1gwHqGuWDk2VuYmQ", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.11MgCe-_uiheyy_kARCwhSZbeq3IkMn40GLQkczQ4Bjn_lkCYfSeqz0HeeYpitksiQ2bW47N0oGKCOYOlmQPyg", token); } } From 1e6f68764b26f62d39b04da6eca878c0989c6e5a Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:01:37 -0300 Subject: [PATCH 51/92] Secrets are no longer Base64 Fixes #2 --- src/main/java/com/auth0/jwt/JWTVerifier.java | 35 +++++++++++++------ .../com/auth0/jwt/JWTVerifyException.java | 16 +++++++-- .../java/com/auth0/jwt/JWTVerifierTest.java | 12 +++---- .../java/com/auth0/jwt/RoundtripTest.java | 10 ++---- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index c1a70f0f..16c98792 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -2,11 +2,15 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -23,22 +27,33 @@ */ public class JWTVerifier { - private final String secret; + private final byte[] secret; private final String audience; private final String issuer; - private final Base64 decoder; + private final Base64 decoder = new Base64(true);; private final ObjectMapper mapper; private Map algorithms; - + public JWTVerifier(String secret, String audience, String issuer) { - if (secret == null || "".equals(secret)) { + this(secret.getBytes(Charset.forName("UTF-8")), audience, issuer); + } + + public JWTVerifier(String secret, String audience) { + this(secret, audience, null); + } + + public JWTVerifier(String secret) { + this(secret, null, null); + } + + public JWTVerifier(byte[] secret, String audience, String issuer) { + if (secret == null || secret.length == 0) { throw new IllegalArgumentException("Secret cannot be null or empty"); } - - decoder = new Base64(true); - mapper = new ObjectMapper(); + + mapper = new ObjectMapper(); algorithms = new HashMap(); algorithms.put("HS256", "HmacSHA256"); @@ -50,11 +65,11 @@ public JWTVerifier(String secret, String audience, String issuer) { this.issuer = issuer; } - public JWTVerifier(String secret, String audience) { + public JWTVerifier(byte[] secret, String audience) { this(secret, audience, null); } - public JWTVerifier(String secret) { + public JWTVerifier(byte[] secret) { this(secret, null, null); } @@ -101,7 +116,7 @@ public Map verify(String token) void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Mac hmac = Mac.getInstance(algorithm); - hmac.init(new SecretKeySpec(decoder.decodeBase64(secret), algorithm)); + hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { diff --git a/src/main/java/com/auth0/jwt/JWTVerifyException.java b/src/main/java/com/auth0/jwt/JWTVerifyException.java index 08bc583b..39ec2039 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifyException.java +++ b/src/main/java/com/auth0/jwt/JWTVerifyException.java @@ -1,10 +1,22 @@ package com.auth0.jwt; public class JWTVerifyException extends Exception { - public JWTVerifyException() { + /** + * + */ + private static final long serialVersionUID = -4911506451239107610L; + + public JWTVerifyException() { } + + + + public JWTVerifyException(String message, Throwable cause) { + super(message, cause); + } + - public JWTVerifyException(String message) { + public JWTVerifyException(String message) { super(message); } } diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b5fa998d..7cdc4cd4 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -14,13 +14,11 @@ import static org.junit.Assert.assertEquals; public class JWTVerifierTest { + + private static final Base64 decoder = new Base64(true);; - @Test(expected = IllegalArgumentException.class) - public void constructorShouldFailOnNullSecret() { - new JWTVerifier(null); - } - - @Test(expected = IllegalArgumentException.class) + + @Test(expected = IllegalArgumentException.class) public void constructorShouldFailOnEmptySecret() { new JWTVerifier(""); } @@ -85,7 +83,7 @@ public void shouldVerifySignature() throws Exception { "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + "." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - final String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + byte[] secret = decoder.decodeBase64("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); new JWTVerifier(secret, "audience") .verifySignature(jws.split("\\."), "HmacSHA256"); } diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java index 53330e77..b4e61144 100644 --- a/src/test/java/com/auth0/jwt/RoundtripTest.java +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -17,17 +17,11 @@ */ public class RoundtripTest { private static final String SECRET; - private static final String SECRET_BASE64; static { - try { - SECRET = "my secret"; - SECRET_BASE64 = Base64.encodeBase64String(SECRET.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + SECRET = "my secret"; } private static JWTSigner signer = new JWTSigner(SECRET); - private static JWTVerifier verifier = new JWTVerifier(SECRET_BASE64); + private static JWTVerifier verifier = new JWTVerifier(SECRET); /* * Roundtrip of different datatypes. From e81ab98be5a7fc9c07137e104cffcc9e923d7260 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:12:31 -0300 Subject: [PATCH 52/92] Fixed docs --- src/main/java/com/auth0/jwt/JWTSigner.java | 35 +++++++++----------- src/main/java/com/auth0/jwt/JWTVerifier.java | 9 +++-- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 84dfc46d..5c2a9420 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -27,7 +27,7 @@ */ public class JWTSigner { private final String secret; - + public JWTSigner(String secret) { this.secret = secret; } @@ -36,7 +36,7 @@ public JWTSigner(String secret) { * Generate a JSON Web Token. * using the default algorithm HMAC SHA-256 ("HS256") * and no claims automatically set. - * + * * @param claims A map of the JWT claims that form the payload. Registered claims * must be of appropriate Java datatype as following: *
    @@ -47,9 +47,8 @@ public JWTSigner(String secret) { * All claims with a null value are left out the JWT. * Any claims set automatically as specified in * the "options" parameter override claims in this map. - * - * @param secret Key to use in signing. Used as-is without Base64 encoding. - * + * + * * @param options Allow choosing the signing algorithm, and automatic setting of some registered claims. */ public String sign(Map claims, Options options) { @@ -72,15 +71,11 @@ public String sign(Map claims, Options options) { /** * Generate a JSON Web Token using the default algorithm HMAC SHA-256 ("HS256") * and no claims automatically set. - * - * @param secret Key to use in signing. Used as-is without Base64 encoding. - * - * For details, see the two parameter variant of this method. */ public String sign(Map claims) { return sign(claims, null); } - + /** * Generate the header part of a JSON web token. */ @@ -99,7 +94,7 @@ private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingExce /** * Generate the JSON web token payload string from the claims. - * @param options + * @param options */ private String encodedPayload(Map _claims, Options options) throws Exception { Map claims = new HashMap(_claims); @@ -110,14 +105,14 @@ private String encodedPayload(Map _claims, Options options) thro enforceIntDate(claims, "nbf"); enforceIntDate(claims, "iat"); enforceString(claims, "jti"); - + if (options != null) processPayloadOptions(claims, options); String payload = new ObjectMapper().writeValueAsString(claims); return base64UrlEncode(payload.getBytes("UTF-8")); } - + private void processPayloadOptions(Map claims, Options options) { long now = System.currentTimeMillis() / 1000l; if (options.expirySeconds != null) @@ -202,7 +197,7 @@ private String checkStringOrURI(Object value) { } return null; } - + /** * Sign the header and payload */ @@ -269,7 +264,7 @@ public static class Options { private Integer notValidBeforeLeeway; private boolean issuedAt; private boolean jwtId; - + public Algorithm getAlgorithm() { return algorithm; } @@ -280,8 +275,8 @@ public Options setAlgorithm(Algorithm algorithm) { this.algorithm = algorithm; return this; } - - + + public Integer getExpirySeconds() { return expirySeconds; } @@ -293,7 +288,7 @@ public Options setExpirySeconds(Integer expirySeconds) { this.expirySeconds = expirySeconds; return this; } - + public Integer getNotValidBeforeLeeway() { return notValidBeforeLeeway; } @@ -305,7 +300,7 @@ public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { this.notValidBeforeLeeway = notValidBeforeLeeway; return this; } - + public boolean isIssuedAt() { return issuedAt; } @@ -317,7 +312,7 @@ public Options setIssuedAt(boolean issuedAt) { this.issuedAt = issuedAt; return this; } - + public boolean isJwtId() { return jwtId; } diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 16c98792..2751e836 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -21,7 +21,6 @@ /** * JWT Java Implementation - *

    * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home * See JWTVerifier.java */ @@ -35,7 +34,7 @@ public class JWTVerifier { private final ObjectMapper mapper; private Map algorithms; - + public JWTVerifier(String secret, String audience, String issuer) { this(secret.getBytes(Charset.forName("UTF-8")), audience, issuer); } @@ -47,12 +46,12 @@ public JWTVerifier(String secret, String audience) { public JWTVerifier(String secret) { this(secret, null, null); } - + public JWTVerifier(byte[] secret, String audience, String issuer) { if (secret == null || secret.length == 0) { throw new IllegalArgumentException("Secret cannot be null or empty"); } - + mapper = new ObjectMapper(); algorithms = new HashMap(); @@ -177,4 +176,4 @@ JsonNode decodeAndParse(String b64String) throws IOException { JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } -} \ No newline at end of file +} From e27ac202d6ee421504f06baee837f672249a0cf4 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:12:53 -0300 Subject: [PATCH 53/92] [maven-release-plugin] prepare release java-jwt-2.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dab8d4b9..d2eed879 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 1.0.1-SNAPSHOT + 2.0.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From b9683f8dbf0ba3d16fb78723f250c78198e92b24 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:13:00 -0300 Subject: [PATCH 54/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d2eed879..a93869bf 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.0 + 2.0.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From b227e8135f259e30c7a718b22030360bba8c2111 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 11:56:25 -0300 Subject: [PATCH 55/92] Using constant-time comparison for signatures. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 2751e836..91decc66 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -12,6 +12,7 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.ArrayList; @@ -118,7 +119,7 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); - if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { + if (!MessageDigest.isEqual(sig, decoder.decodeBase64(pieces[2]))) { throw new SignatureException("signature verification failed"); } } From aae6118fef7ea5081719b39fad9181eab02f292f Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 14:04:25 -0300 Subject: [PATCH 56/92] [maven-release-plugin] prepare release java-jwt-2.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a93869bf..a6d49fb7 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.1-SNAPSHOT + 2.0.1 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 6b5b7bab20ca314c7759c2fd71725fea1ef52cdf Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 14:04:30 -0300 Subject: [PATCH 57/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a6d49fb7..45d6af06 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.1 + 2.0.2-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 725e3eae280b61fca27351ffa3890f209f26b428 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:51:58 -0800 Subject: [PATCH 58/92] Changed secret data type from string to bytes. --- src/main/java/com/auth0/jwt/JWTSigner.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 5c2a9420..e98c3522 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,9 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final String secret; + private final byte[] secret; - public JWTSigner(String secret) { + public JWTSigner(byte[] secret) { this.secret = secret; } @@ -216,7 +216,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +233,9 @@ private static byte[] sign(Algorithm algorithm, String msg, String secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + mac.init(new SecretKeySpec(secret, algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From 5387505b61376f83ca5299e461a71f61e8284ec5 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:52:33 -0800 Subject: [PATCH 59/92] Revert "Changed secret data type from string to bytes." This reverts commit 725e3eae280b61fca27351ffa3890f209f26b428. --- src/main/java/com/auth0/jwt/JWTSigner.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index e98c3522..5c2a9420 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,9 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final byte[] secret; + private final String secret; - public JWTSigner(byte[] secret) { + public JWTSigner(String secret) { this.secret = secret; } @@ -216,7 +216,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +233,9 @@ private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret, algorithm.getValue())); + mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From a9bbdfb3b386598a111e9e8e4e2537c75e0d4c32 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:56:59 -0800 Subject: [PATCH 60/92] Changed storage of secret to use an array of bytes instead of a string. Added new constructor to accept the array of bytes. --- src/main/java/com/auth0/jwt/JWTSigner.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 5c2a9420..3468bfd2 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,13 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final String secret; + private final byte[] secret; public JWTSigner(String secret) { + this(secret.getBytes()); + } + + public JWTSigner(byte[] secret) { this.secret = secret; } @@ -216,7 +220,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +237,9 @@ private static byte[] sign(Algorithm algorithm, String msg, String secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + mac.init(new SecretKeySpec(secret, algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From e21dd12ac14343500d80984a7f4595993cbd79e0 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 16:23:17 -0800 Subject: [PATCH 61/92] Added unit test to test byte array-constructed signer. --- .../com/auth0/jwt/JWTSignerTest_Bytes.java | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java b/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java new file mode 100644 index 00000000..93cffda4 --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java @@ -0,0 +1,200 @@ +package com.auth0.jwt; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; + +import static org.junit.Assert.assertEquals; + +public class JWTSignerTest_Bytes { + private static JWTSigner signer = new JWTSigner(new byte[] { 109, 121, 32, 115, 101, 99, 114, 101, 116}); + + @Test + public void shouldSignEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignEmptyTwoParams() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); + } + + @Test + public void shouldSignStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", "http://foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); + } + + @Test + public void shouldSignStringOrURI3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", ""); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); + } + + @Test + public void shouldSignStringOrURICollection() throws Exception { + HashMap claims = new HashMap(); + LinkedList aud = new LinkedList(); + aud.add("xyz"); + aud.add("ftp://foo"); + claims.put("aud", aud); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); + } + + @Test + public void shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void bytes_shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignIntDate3() throws Exception { + HashMap claims = new HashMap(); + claims.put("iat", Long.MAX_VALUE); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); + } + + @Test + public void bytes_shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); + } + + @Test + public void shouldSignNullEqualsMissing() throws Exception { + HashMap claims = new HashMap(); + for (String claimName : Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")) { + claims.put(claimName, null); + } + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", ":"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection1() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection2() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(0)); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(":")); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", -1); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", "100"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", 100); + signer.sign(claims); + } + + @Test + public void shouldOptionsNone() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, new JWTSigner.Options()); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldOptionsAll() throws Exception { + HashMap claims = new HashMap(); + signer.sign(claims, new JWTSigner.Options() + .setExpirySeconds(1000).setNotValidBeforeLeeway(5) + .setIssuedAt(true).setJwtId(true)); + } + + @Test + public void shouldOptionsAlgorithm() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.11MgCe-_uiheyy_kARCwhSZbeq3IkMn40GLQkczQ4Bjn_lkCYfSeqz0HeeYpitksiQ2bW47N0oGKCOYOlmQPyg", token); + } + +} From c33fe66c208bb490c2c9a1b1b3b50c3763f5014d Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 26 Jan 2015 18:42:45 -0300 Subject: [PATCH 62/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 940c35c0..c7a284e4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 1.0.0 + 2.0.1 ``` From 53d2a14711673c89c2b409ca7c54deae22e11b6a Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Tue, 27 Jan 2015 13:22:28 -0800 Subject: [PATCH 63/92] Update and rename LICENSE.md to LICENSE.txt --- LICENSE.md | 20 -------------------- LICENSE.txt | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 LICENSE.md create mode 100644 LICENSE.txt diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 8258c89c..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Auth0, Inc - -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. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..bcd1854c --- /dev/null +++ b/LICENSE.txt @@ -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. From 4cf0704f7824a41667a3d2884ca4ff392f02429f Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Tue, 27 Jan 2015 13:23:16 -0800 Subject: [PATCH 64/92] Update README.md --- README.md | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c7a284e4..198bcde9 100644 --- a/README.md +++ b/README.md @@ -38,36 +38,17 @@ Yes, here you are: Most of the code have been written by Luis Faja . We just wrapped it in a nicer interface and published it to Maven Central. We'll be adding support for signing and other algorithms in the future. -### FAQ - - -#### Why another JSON Web Token implementation for Java? +### Why another JSON Web Token implementation for Java? We think that current JWT implementations are either too complex or not tested enough. We want something simple with the right number of abstractions. ## Issue Reporting 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. -## License - -The MIT License (MIT) - -Copyright (c) 2014 Auth0, Inc. +## Author -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: +[Auth0](auth0.com) -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +## License -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. +This project is licensed under the MIT license. See the [LICENSE](LICENSE.txt) file for more info. From 044e4fd75cd2b524faa24b66476a103cc07b50d2 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 1 Mar 2015 15:19:04 -0300 Subject: [PATCH 65/92] [maven-release-plugin] prepare release java-jwt-2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45d6af06..561fa0e4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.2-SNAPSHOT + 2.1.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From e040916f016af65f1bbef870c19737f75bae976b Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 1 Mar 2015 15:19:11 -0300 Subject: [PATCH 66/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 561fa0e4..7019448c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.1.0 + 2.1.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From a8c310356de384ff7824bc7362c3fd6e7069d8d9 Mon Sep 17 00:00:00 2001 From: Tom Haggie Date: Thu, 2 Apr 2015 01:11:32 -0700 Subject: [PATCH 67/92] JWTVerifier was creating a URL safe base 64 decoder but wasn't using it as it was using the non URL safe static methods on Base64. I'm assuming here that a) these things are wanted to be decoded URL safe b) the verifier isn't expected to be thread safe. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 91decc66..89e91486 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,25 +1,22 @@ package com.auth0.jwt; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.apache.commons.codec.binary.Base64; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * JWT Java Implementation * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home @@ -119,7 +116,7 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); - if (!MessageDigest.isEqual(sig, decoder.decodeBase64(pieces[2]))) { + if (!MessageDigest.isEqual(sig, decoder.decode(pieces[2]))) { throw new SignatureException("signature verification failed"); } } @@ -173,7 +170,7 @@ String getAlgorithm(JsonNode jwtHeader) { } JsonNode decodeAndParse(String b64String) throws IOException { - String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); + String jsonString = new String(decoder.decode(b64String), "UTF-8"); JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } From 72a989a84ccd23e7f34aeed80ce289024b570d65 Mon Sep 17 00:00:00 2001 From: christopherthielen Date: Tue, 21 Apr 2015 19:24:59 -0500 Subject: [PATCH 68/92] fix(test): Fix race condition in RoundtripTest.shouldOptionsIat closes #28 --- src/test/java/com/auth0/jwt/RoundtripTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java index b4e61144..35299337 100644 --- a/src/test/java/com/auth0/jwt/RoundtripTest.java +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -97,15 +97,17 @@ public void shouldBoolean() throws Exception { @Test public void shouldOptionsIat() throws Exception { HashMap claims = new HashMap(); - String token = signer.sign(claims, - new JWTSigner.Options().setIssuedAt(true)); + long before = System.currentTimeMillis(); + String token = signer.sign(claims, new JWTSigner.Options().setIssuedAt(true)); + long after = System.currentTimeMillis(); Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); long iat = ((Number) decoded.get("iat")).longValue(); - assertTrue(iat >= System.currentTimeMillis() / 1000l); - assertTrue(iat < System.currentTimeMillis() / 1000l + 10); + assertTrue(iat >= before / 1000l); + assertTrue(iat <= after / 1000l); } - + @Test public void shouldOptionsTimestamps() throws Exception { HashMap claims = new HashMap(); From 588cd8714149ac9621c5ee3561d67dd6f05890b9 Mon Sep 17 00:00:00 2001 From: nicholasle Date: Wed, 27 May 2015 10:51:59 +0900 Subject: [PATCH 69/92] Update README.md Update the sample application to make it works for Auth0's users. Update maven to the latest version of java-jwt 2.1.0 --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 198bcde9..b90b5f9b 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,18 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. ### Usage +Note for Auth0 users: +By default, Auth0's CLIENT_SECRET is base64-encoded. +To work with JWTVerifier, it must be decoded first. ```java public class Application { public static void main (String [] args) { try { + Base64 decoder = new Base64(true); + byte[] secret = decoder.decodeBase64(CLIENT_SECRET); Map decodedPayload = - new JWTVerifier("secret", "audience").verify("my-token"); + new JWTVerifier(secret, "audience").verify("my-token"); // Get custom fields from decoded Payload System.out.println(decodedPayload.get("name")); @@ -30,7 +35,7 @@ Yes, here you are: com.auth0 java-jwt - 2.0.1 + 2.1.0 ``` From 111023b234d5f11e9a58c52af3617ef1c99f73ce Mon Sep 17 00:00:00 2001 From: Richard Seldon Date: Sat, 9 Jul 2016 11:20:34 +0900 Subject: [PATCH 70/92] RSA Support --- README.md | 86 ++++-- pom.xml | 120 +++++--- src/main/java/com/auth0/jwt/Algorithm.java | 24 +- .../com/auth0/jwt/JWTAlgorithmException.java | 20 ++ .../com/auth0/jwt/JWTAudienceException.java | 15 +- .../com/auth0/jwt/JWTExpiredException.java | 9 +- .../com/auth0/jwt/JWTIssuerException.java | 12 +- src/main/java/com/auth0/jwt/JWTSigner.java | 278 ++++++++++-------- src/main/java/com/auth0/jwt/JWTVerifier.java | 208 ++++++++----- .../com/auth0/jwt/JWTVerifyException.java | 20 +- .../java/com/auth0/jwt/pem/PemFileReader.java | 38 +++ .../java/com/auth0/jwt/pem/PemFileWriter.java | 31 ++ .../java/com/auth0/jwt/pem/PemReader.java | 68 +++++ .../java/com/auth0/jwt/pem/PemWriter.java | 34 +++ .../java/com/auth0/jwt/pem/X509CertUtils.java | 61 ++++ .../com/auth0/jwt/JWTRoundTripRsa256Test.java | 103 +++++++ ...undtripTest.java => JWTRoundTripTest.java} | 36 ++- .../com/auth0/jwt/JWTSignerBytesTest.java | 205 +++++++++++++ .../java/com/auth0/jwt/JWTSignerTest.java | 47 +-- .../com/auth0/jwt/JWTVerifierRsa256Test.java | 85 ++++++ .../java/com/auth0/jwt/JWTVerifierTest.java | 24 +- src/test/java/com/auth0/jwt/User.java | 29 -- src/test/resources/auth0-pem/key.pem | 18 ++ src/test/resources/test-pem/test-auth0.key | 27 ++ src/test/resources/test-pem/test-auth0.pem | 24 ++ 25 files changed, 1256 insertions(+), 366 deletions(-) create mode 100644 src/main/java/com/auth0/jwt/JWTAlgorithmException.java create mode 100644 src/main/java/com/auth0/jwt/pem/PemFileReader.java create mode 100644 src/main/java/com/auth0/jwt/pem/PemFileWriter.java create mode 100644 src/main/java/com/auth0/jwt/pem/PemReader.java create mode 100644 src/main/java/com/auth0/jwt/pem/PemWriter.java create mode 100644 src/main/java/com/auth0/jwt/pem/X509CertUtils.java create mode 100644 src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java rename src/test/java/com/auth0/jwt/{RoundtripTest.java => JWTRoundTripTest.java} (90%) create mode 100644 src/test/java/com/auth0/jwt/JWTSignerBytesTest.java create mode 100644 src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java delete mode 100644 src/test/java/com/auth0/jwt/User.java create mode 100644 src/test/resources/auth0-pem/key.pem create mode 100644 src/test/resources/test-pem/test-auth0.key create mode 100644 src/test/resources/test-pem/test-auth0.pem diff --git a/README.md b/README.md index b90b5f9b..0afc9d20 100644 --- a/README.md +++ b/README.md @@ -2,49 +2,73 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. -### Usage -Note for Auth0 users: -By default, Auth0's CLIENT_SECRET is base64-encoded. -To work with JWTVerifier, it must be decoded first. +## Installation -```java -public class Application { - public static void main (String [] args) { - try { - Base64 decoder = new Base64(true); - byte[] secret = decoder.decodeBase64(CLIENT_SECRET); - Map decodedPayload = - new JWTVerifier(secret, "audience").verify("my-token"); - - // Get custom fields from decoded Payload - System.out.println(decodedPayload.get("name")); - } catch (SignatureException signatureException) { - System.err.println("Invalid signature!"); - } catch (IllegalStateException illegalStateException) { - System.err.println("Invalid Token! " + illegalStateException); - } - } -} -``` - -#### Maven coordinates? - -Yes, here you are: +### Maven ```xml com.auth0 java-jwt - 2.1.0 + 2.2.1 ``` -### Credits +### Gradle + +```gradle +compile 'com.auth0.java-jwt:2.2.1' +``` + +## Usage + +### Sign JWT (HS256) + +```java +final String issuer = "https://mydomain.com/"; +final String secret = "{{a secret used for signing}}"; + +final long iat = System.currentTimeMillis() / 1000l; // issued at claim +final long exp = iat + 60L; // expires claim. In this case the token expires in 60 seconds + +final JWTSigner signer = new JWTSigner(secret); +final HashMap claims = new HashMap(); +claims.put("iss", issuer); +claims.put("exp", exp); +claims.put("iat", iat); + +final String jwt = signer.sign(claims); +``` + +### Verify JWT (HS256) + +```java +final String secret = "{{secret used for signing}}"; +try { + final JWTVerifier verifier = new JWTVerifier(secret); + final Map claims= jwtVerifier.verify(jwt); +} catch (JWTVerifyException e) { + // Invalid Token +} +``` + +### Validate aud & iss claims + +```java +final String secret = "{{secret used for signing}}"; +try { + final JWTVerifier verifier = new JWTVerifier(secret, "{{my-audience}}", "{{my-issuer}}"); + final Map claims= jwtVerifier.verify(jwt); +} catch (JWTVerifyException e) { + // Invalid Token +} +``` -Most of the code have been written by Luis Faja . We just wrapped it in a nicer interface and published it to Maven Central. We'll be adding support for signing and other algorithms in the future. ### Why another JSON Web Token implementation for Java? -We think that current JWT implementations are either too complex or not tested enough. We want something simple with the right number of abstractions. + +We believe existing JWT implementations in Java are either too complex or not tested enough. +This library aims to be simple and achieve the right level of abstraction. ## Issue Reporting diff --git a/pom.xml b/pom.xml index 7019448c..a4089c0c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -9,14 +10,15 @@ com.auth0 java-jwt - 2.1.1-SNAPSHOT + 2.2.1-SNAPSHOT Java JWT - Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io - 1.5 + 1.7 com.auth0.jwt.internal @@ -28,16 +30,6 @@ - - - Alberto Pose - pose - - Developer - - - - https://github.com/auth0/java-jwt scm:git:git@github.com:auth0/java-jwt.git @@ -45,24 +37,46 @@ - + com.fasterxml.jackson.core jackson-databind 2.0.1 + + + org.bouncycastle + bcprov-jdk15on + 1.52 + + commons-codec commons-codec 1.4 + + org.apache.commons + commons-lang3 + 3.4 + + + + org.apache.commons + commons-io + 1.3.2 + + + + junit junit 4.11 test + @@ -77,33 +91,55 @@ UTF-8 - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - false - true - - - com.fasterxml.jackson - ${repackage.base}.com.fasterxml.jackson - - - org.apache.commons.codec - ${repackage.base}.org.apache.commons.codec - - - - - - + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + false + true + + + com.fasterxml.jackson + ${repackage.base}.com.fasterxml.jackson + + + org.apache.commons.codec + ${repackage.base}.org.apache.commons.codec + + + org.apache.commons.io + ${repackage.base}.org.apache.commons.io + + + org.apache.commons.lang3 + ${repackage.base}.org.apache.commons.lang3 + + + org.bouncycastle + ${repackage.base}.org.bouncycastle + + + + + + diff --git a/src/main/java/com/auth0/jwt/Algorithm.java b/src/main/java/com/auth0/jwt/Algorithm.java index be135d35..b3009a73 100644 --- a/src/main/java/com/auth0/jwt/Algorithm.java +++ b/src/main/java/com/auth0/jwt/Algorithm.java @@ -1,15 +1,33 @@ package com.auth0.jwt; +import org.apache.commons.lang3.Validate; + +/** + * Supported Library Algorithms + * + * https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator + */ public enum Algorithm { - HS256("HmacSHA256"), HS384("HmacSHA384"), HS512("HmacSHA512"), RS256("RS256"), RS384("RS384"), RS512("RS512"); - private Algorithm(String value) { + HS256("HmacSHA256"), HS384("HmacSHA384"), HS512("HmacSHA512"), RS256("SHA256withRSA"), RS384("SHA384withRSA"), RS512("SHA512withRSA"); + + private Algorithm(final String value) { this.value = value; } - + private String value; public String getValue() { return value; } + + public static Algorithm findByName(final String name) throws JWTAlgorithmException { + Validate.notNull(name); + try { + return Algorithm.valueOf(name); + } catch (IllegalArgumentException e) { + throw new JWTAlgorithmException("Unsupported algorithm: " + name); + } + } + } diff --git a/src/main/java/com/auth0/jwt/JWTAlgorithmException.java b/src/main/java/com/auth0/jwt/JWTAlgorithmException.java new file mode 100644 index 00000000..fec47b3e --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTAlgorithmException.java @@ -0,0 +1,20 @@ +package com.auth0.jwt; + +/** + * Represents Exception related to Algorithm - for example JWT header algorithm is unsupported / missing + */ +public class JWTAlgorithmException extends JWTVerifyException { + + + public JWTAlgorithmException() {} + + public JWTAlgorithmException(final String message, final Throwable cause) { + super(message, cause); + } + + public JWTAlgorithmException(final String message) { + super(message); + } + +} + diff --git a/src/main/java/com/auth0/jwt/JWTAudienceException.java b/src/main/java/com/auth0/jwt/JWTAudienceException.java index 6c3da263..dc6192a0 100644 --- a/src/main/java/com/auth0/jwt/JWTAudienceException.java +++ b/src/main/java/com/auth0/jwt/JWTAudienceException.java @@ -1,26 +1,33 @@ package com.auth0.jwt; import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.lang3.Validate; import java.util.ArrayList; import java.util.List; +/** + * Represents Exception related to Audience - for example illegal audience on JWT Verification + */ public class JWTAudienceException extends JWTVerifyException { + private JsonNode audienceNode; - public JWTAudienceException(JsonNode audienceNode) { + public JWTAudienceException(final JsonNode audienceNode) { + Validate.notNull(audienceNode); this.audienceNode = audienceNode; } - public JWTAudienceException(String message, JsonNode audienceNode) { + public JWTAudienceException(final String message, final JsonNode audienceNode) { super(message); + Validate.notNull(audienceNode); this.audienceNode = audienceNode; } public List getAudience() { - ArrayList audience = new ArrayList(); + final ArrayList audience = new ArrayList<>(); if (audienceNode.isArray()) { - for (JsonNode jsonNode : audienceNode) { + for (final JsonNode jsonNode : audienceNode) { audience.add(jsonNode.textValue()); } } else if (audienceNode.isTextual()) { diff --git a/src/main/java/com/auth0/jwt/JWTExpiredException.java b/src/main/java/com/auth0/jwt/JWTExpiredException.java index 50178aa8..41086eef 100644 --- a/src/main/java/com/auth0/jwt/JWTExpiredException.java +++ b/src/main/java/com/auth0/jwt/JWTExpiredException.java @@ -1,13 +1,18 @@ package com.auth0.jwt; + +/** + * Represents Exception related to Expiration - for example JWT token has expired + */ public class JWTExpiredException extends JWTVerifyException { + private long expiration; - public JWTExpiredException(long expiration) { + public JWTExpiredException(final long expiration) { this.expiration = expiration; } - public JWTExpiredException(String message, long expiration) { + public JWTExpiredException(final String message, final long expiration) { super(message); this.expiration = expiration; } diff --git a/src/main/java/com/auth0/jwt/JWTIssuerException.java b/src/main/java/com/auth0/jwt/JWTIssuerException.java index 4dc1cf16..7f0b1cd4 100644 --- a/src/main/java/com/auth0/jwt/JWTIssuerException.java +++ b/src/main/java/com/auth0/jwt/JWTIssuerException.java @@ -1,14 +1,22 @@ package com.auth0.jwt; +import org.apache.commons.lang3.Validate; + +/** + * Represents Exception related to Issuer - for example issuer mismatch / missing upon verification + */ public class JWTIssuerException extends JWTVerifyException { + private final String issuer; - public JWTIssuerException(String issuer) { + public JWTIssuerException(final String issuer) { + Validate.notNull(issuer); this.issuer = issuer; } - public JWTIssuerException(String message, String issuer) { + public JWTIssuerException(final String message, final String issuer) { super(message); + Validate.notNull(issuer); this.issuer = issuer; } diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 3468bfd2..89f2dc07 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -1,107 +1,114 @@ package com.auth0.jwt; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.naming.OperationNotSupportedException; - -import org.apache.commons.codec.binary.Base64; - import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.*; +import java.util.*; /** - * JwtSigner implementation based on the Ruby implementation from http://jwt.io - * No support for RSA encryption at present + * Handles JWT Sign Operation + * + * Default algorithm when none provided is HMAC SHA-256 ("HS256") + * + * See associated library test cases for clear examples on usage + * */ public class JWTSigner { - private final byte[] secret; - public JWTSigner(String secret) { + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private byte[] secret; + private PrivateKey privateKey; + + // Default algorithm HMAC SHA-256 ("HS256") + protected final static Algorithm DEFAULT_ALGORITHM = Algorithm.HS256; + + public JWTSigner(final String secret) { this(secret.getBytes()); } - public JWTSigner(byte[] secret) { + public JWTSigner(final byte[] secret) { + Validate.notNull(secret); this.secret = secret; } + public JWTSigner(final PrivateKey privateKey) { + this.privateKey = privateKey; + } + /** * Generate a JSON Web Token. - * using the default algorithm HMAC SHA-256 ("HS256") - * and no claims automatically set. - * - * @param claims A map of the JWT claims that form the payload. Registered claims - * must be of appropriate Java datatype as following: - *

      - *
    • iss, sub: String - *
    • exp, nbf, iat, jti: numeric, eg. Long - *
    • aud: String, or Collection<String> - *
    - * All claims with a null value are left out the JWT. - * Any claims set automatically as specified in - * the "options" parameter override claims in this map. - * * + * @param claims A map of the JWT claims that form the payload. Registered claims + * must be of appropriate Java datatype as following: + *
      + *
    • iss, sub: String + *
    • exp, nbf, iat, jti: numeric, eg. Long + *
    • aud: String, or Collection<String> + *
    + * All claims with a null value are left out the JWT. + * Any claims set automatically as specified in + * the "options" parameter override claims in this map. * @param options Allow choosing the signing algorithm, and automatic setting of some registered claims. */ - public String sign(Map claims, Options options) { - Algorithm algorithm = Algorithm.HS256; - if (options != null && options.algorithm != null) - algorithm = options.algorithm; - - List segments = new ArrayList(); + public String sign(final Map claims, final Options options) { + Validate.notNull(claims); + final Algorithm algorithm = (options != null && options.algorithm != null) ? options.algorithm : DEFAULT_ALGORITHM; + final List segments = new ArrayList<>(); try { segments.add(encodedHeader(algorithm)); segments.add(encodedPayload(claims, options)); segments.add(encodedSignature(join(segments, "."), algorithm)); + return join(segments, "."); } catch (Exception e) { - throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e); + throw new RuntimeException(e.getCause()); } - - return join(segments, "."); } /** * Generate a JSON Web Token using the default algorithm HMAC SHA-256 ("HS256") * and no claims automatically set. */ - public String sign(Map claims) { + public String sign(final Map claims) { + Validate.notNull(claims); return sign(claims, null); } /** * Generate the header part of a JSON web token. */ - private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingException { - if (algorithm == null) { // default the algorithm if not specified - algorithm = Algorithm.HS256; - } - + private String encodedHeader(final Algorithm algorithm) throws UnsupportedEncodingException { + Validate.notNull(algorithm); // create the header - ObjectNode header = JsonNodeFactory.instance.objectNode(); + final ObjectNode header = JsonNodeFactory.instance.objectNode(); header.put("typ", "JWT"); header.put("alg", algorithm.name()); - return base64UrlEncode(header.toString().getBytes("UTF-8")); } /** * Generate the JSON web token payload string from the claims. + * * @param options */ - private String encodedPayload(Map _claims, Options options) throws Exception { - Map claims = new HashMap(_claims); + private String encodedPayload(final Map _claims, final Options options) throws IOException { + final Map claims = new HashMap<>(_claims); enforceStringOrURI(claims, "iss"); enforceStringOrURI(claims, "sub"); enforceStringOrURICollection(claims, "aud"); @@ -109,16 +116,17 @@ private String encodedPayload(Map _claims, Options options) thro enforceIntDate(claims, "nbf"); enforceIntDate(claims, "iat"); enforceString(claims, "jti"); - - if (options != null) + if (options != null) { processPayloadOptions(claims, options); - - String payload = new ObjectMapper().writeValueAsString(claims); + } + final String payload = new ObjectMapper().writeValueAsString(claims); return base64UrlEncode(payload.getBytes("UTF-8")); } - private void processPayloadOptions(Map claims, Options options) { - long now = System.currentTimeMillis() / 1000l; + private void processPayloadOptions(final Map claims, final Options options) { + Validate.notNull(claims); + Validate.notNull(options); + final long now = System.currentTimeMillis() / 1000l; if (options.expirySeconds != null) claims.put("exp", now + options.expirySeconds); if (options.notValidBeforeLeeway != null) @@ -129,58 +137,65 @@ private void processPayloadOptions(Map claims, Options options) claims.put("jti", UUID.randomUUID().toString()); } - private void enforceIntDate(Map claims, String claimName) { - Object value = handleNullValue(claims, claimName); + // consider cleanup + private void enforceIntDate(final Map claims, final String claimName) { + Validate.notNull(claims); + Validate.notNull(claimName); + final Object value = handleNullValue(claims, claimName); if (value == null) return; if (!(value instanceof Number)) { - throw new RuntimeException(String.format("Claim '%s' is invalid: must be an instance of Number", claimName)); + throw new IllegalStateException(String.format("Claim '%s' is invalid: must be an instance of Number", claimName)); } - long longValue = ((Number) value).longValue(); + final long longValue = ((Number) value).longValue(); if (longValue < 0) - throw new RuntimeException(String.format("Claim '%s' is invalid: must be non-negative", claimName)); + throw new IllegalStateException(String.format("Claim '%s' is invalid: must be non-negative", claimName)); claims.put(claimName, longValue); } - private void enforceStringOrURICollection(Map claims, String claimName) { - Object values = handleNullValue(claims, claimName); + // consider cleanup + private void enforceStringOrURICollection(final Map claims, final String claimName) { + final Object values = handleNullValue(claims, claimName); if (values == null) return; if (values instanceof Collection) { - @SuppressWarnings({ "unchecked" }) - Iterator iterator = ((Collection) values).iterator(); + @SuppressWarnings({"unchecked"}) + final Iterator iterator = ((Collection) values).iterator(); while (iterator.hasNext()) { Object value = iterator.next(); String error = checkStringOrURI(value); if (error != null) - throw new RuntimeException(String.format("Claim 'aud' element is invalid: %s", error)); + throw new IllegalStateException(String.format("Claim 'aud' element is invalid: %s", error)); } } else { enforceStringOrURI(claims, "aud"); } } - private void enforceStringOrURI(Map claims, String claimName) { - Object value = handleNullValue(claims, claimName); + // consider cleanup + private void enforceStringOrURI(final Map claims, final String claimName) { + final Object value = handleNullValue(claims, claimName); if (value == null) return; - String error = checkStringOrURI(value); + final String error = checkStringOrURI(value); if (error != null) - throw new RuntimeException(String.format("Claim '%s' is invalid: %s", claimName, error)); + throw new IllegalStateException(String.format("Claim '%s' is invalid: %s", claimName, error)); } - private void enforceString(Map claims, String claimName) { - Object value = handleNullValue(claims, claimName); + // consider cleanup + private void enforceString(final Map claims, final String claimName) { + final Object value = handleNullValue(claims, claimName); if (value == null) return; if (!(value instanceof String)) - throw new RuntimeException(String.format("Claim '%s' is invalid: not a string", claimName)); + throw new IllegalStateException(String.format("Claim '%s' is invalid: not a string", claimName)); } - private Object handleNullValue(Map claims, String claimName) { - if (! claims.containsKey(claimName)) + // consider cleanup + private Object handleNullValue(final Map claims, final String claimName) { + if (!claims.containsKey(claimName)) return null; - Object value = claims.get(claimName); + final Object value = claims.get(claimName); if (value == null) { claims.remove(claimName); return null; @@ -188,10 +203,11 @@ private Object handleNullValue(Map claims, String claimName) { return value; } - private String checkStringOrURI(Object value) { + // consider cleanup + private String checkStringOrURI(final Object value) { if (!(value instanceof String)) return "not a string"; - String stringOrUri = (String) value; + final String stringOrUri = (String) value; if (!stringOrUri.contains(":")) return null; try { @@ -205,57 +221,63 @@ private String checkStringOrURI(Object value) { /** * Sign the header and payload */ - private String encodedSignature(String signingInput, Algorithm algorithm) throws Exception { - byte[] signature = sign(algorithm, signingInput, secret); - return base64UrlEncode(signature); + private String encodedSignature(final String signingInput, final Algorithm algorithm) throws NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException, JWTAlgorithmException { + Validate.notNull(signingInput); + Validate.notNull(algorithm); + switch (algorithm) { + case HS256: + case HS384: + case HS512: + return base64UrlEncode(signHmac(algorithm, signingInput, secret)); + case RS256: + case RS384: + case RS512: + return base64UrlEncode(signRs(algorithm, signingInput, privateKey)); + default: + throw new JWTAlgorithmException("Unsupported signing method"); + } } /** * Safe URL encode a byte array to a String */ - private String base64UrlEncode(byte[] str) { + private String base64UrlEncode(final byte[] str) { + Validate.notNull(str); return new String(Base64.encodeBase64URLSafe(str)); } - /** - * Switch the signing algorithm based on input, RSA not supported - */ - private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { - switch (algorithm) { - case HS256: - case HS384: - case HS512: - return signHmac(algorithm, msg, secret); - case RS256: - case RS384: - case RS512: - default: - throw new OperationNotSupportedException("Unsupported signing method"); - } - } - /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { - Mac mac = Mac.getInstance(algorithm.getValue()); + private static byte[] signHmac(final Algorithm algorithm, final String msg, final byte[] secret) throws NoSuchAlgorithmException, InvalidKeyException { + Validate.notNull(algorithm); + Validate.notNull(msg); + Validate.notNull(secret); + final Mac mac = Mac.getInstance(algorithm.getValue()); mac.init(new SecretKeySpec(secret, algorithm.getValue())); return mac.doFinal(msg.getBytes()); } - private String join(List input, String on) { - int size = input.size(); - int count = 1; - StringBuilder joined = new StringBuilder(); - for (String string : input) { - joined.append(string); - if (count < size) { - joined.append(on); - } - count++; - } + /** + * Sign an input string using RSA and return the encrypted bytes + */ + private static byte[] signRs(final Algorithm algorithm, final String msg, final PrivateKey privateKey) throws NoSuchProviderException, + NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Validate.notNull(algorithm); + Validate.notNull(msg); + Validate.notNull(privateKey); + final byte[] messageBytes = msg.getBytes(); + final Signature signature = Signature.getInstance(algorithm.getValue(), "BC"); + signature.initSign(privateKey); + signature.update(messageBytes); + return signature.sign(); + } - return joined.toString(); + private String join(final List input, final String separator) { + Validate.notNull(input); + Validate.notNull(separator); + return StringUtils.join(input.iterator(), separator); } /** @@ -263,6 +285,7 @@ private String join(List input, String on) { * claims to be automatically set. */ public static class Options { + private Algorithm algorithm; private Integer expirySeconds; private Integer notValidBeforeLeeway; @@ -272,10 +295,11 @@ public static class Options { public Algorithm getAlgorithm() { return algorithm; } + /** - * Algorithm to sign JWT with. Default is HS256. + * Algorithm to sign JWT with. */ - public Options setAlgorithm(Algorithm algorithm) { + public Options setAlgorithm(final Algorithm algorithm) { this.algorithm = algorithm; return this; } @@ -284,11 +308,12 @@ public Options setAlgorithm(Algorithm algorithm) { public Integer getExpirySeconds() { return expirySeconds; } + /** * Set JWT claim "exp" to current timestamp plus this value. * Overrides content of claims in sign(). */ - public Options setExpirySeconds(Integer expirySeconds) { + public Options setExpirySeconds(final Integer expirySeconds) { this.expirySeconds = expirySeconds; return this; } @@ -296,11 +321,12 @@ public Options setExpirySeconds(Integer expirySeconds) { public Integer getNotValidBeforeLeeway() { return notValidBeforeLeeway; } + /** * Set JWT claim "nbf" to current timestamp minus this value. * Overrides content of claims in sign(). */ - public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { + public Options setNotValidBeforeLeeway(final Integer notValidBeforeLeeway) { this.notValidBeforeLeeway = notValidBeforeLeeway; return this; } @@ -308,11 +334,12 @@ public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { public boolean isIssuedAt() { return issuedAt; } + /** * Set JWT claim "iat" to current timestamp. Defaults to false. * Overrides content of claims in sign(). */ - public Options setIssuedAt(boolean issuedAt) { + public Options setIssuedAt(final boolean issuedAt) { this.issuedAt = issuedAt; return this; } @@ -320,13 +347,16 @@ public Options setIssuedAt(boolean issuedAt) { public boolean isJwtId() { return jwtId; } + /** * Set JWT claim "jti" to a pseudo random unique value (type 4 UUID). Defaults to false. * Overrides content of claims in sign(). */ - public Options setJwtId(boolean jwtId) { + public Options setJwtId(final boolean jwtId) { this.jwtId = jwtId; return this; } + } + } diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 89e91486..9696a831 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,150 +1,205 @@ package com.auth0.jwt; -import java.io.IOException; -import java.nio.charset.Charset; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.HashMap; -import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.Validate; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.*; +import java.util.Map; /** - * JWT Java Implementation - * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home - * See JWTVerifier.java + * Handles JWT Verification Operations + * + * Validates claims and signature + * + * See associated library test cases for clear examples on usage + * */ public class JWTVerifier { - private final byte[] secret; + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private byte[] secret; + private PublicKey publicKey; private final String audience; private final String issuer; - private final Base64 decoder = new Base64(true);; + private final Base64 decoder = new Base64(true); private final ObjectMapper mapper; - private Map algorithms; - public JWTVerifier(String secret, String audience, String issuer) { + public JWTVerifier(final String secret, final String audience, final String issuer) { this(secret.getBytes(Charset.forName("UTF-8")), audience, issuer); } - public JWTVerifier(String secret, String audience) { + public JWTVerifier(final String secret, final String audience) { this(secret, audience, null); } - public JWTVerifier(String secret) { + public JWTVerifier(final String secret) { this(secret, null, null); } - public JWTVerifier(byte[] secret, String audience, String issuer) { + public JWTVerifier(final byte[] secret, final String audience) { + this(secret, audience, null); + } + + public JWTVerifier(final byte[] secret) { + this(secret, null, null); + } + + public JWTVerifier(final byte[] secret, final String audience, final String issuer) { if (secret == null || secret.length == 0) { throw new IllegalArgumentException("Secret cannot be null or empty"); } - - mapper = new ObjectMapper(); - - algorithms = new HashMap(); - algorithms.put("HS256", "HmacSHA256"); - algorithms.put("HS384", "HmacSHA384"); - algorithms.put("HS512", "HmacSHA512"); - + mapper = new ObjectMapper(); this.secret = secret; this.audience = audience; this.issuer = issuer; } - public JWTVerifier(byte[] secret, String audience) { - this(secret, audience, null); + public JWTVerifier(final PublicKey publicKey, final String audience, final String issuer) { + Validate.notNull(publicKey); + mapper = new ObjectMapper(); + this.publicKey = publicKey; + this.audience = audience; + this.issuer = issuer; } - public JWTVerifier(byte[] secret) { - this(secret, null, null); + public JWTVerifier(final PublicKey publicKey, final String audience) { + this(publicKey, audience, null); + } + + public JWTVerifier(final PublicKey publicKey) { + this(publicKey, null, null); } + /** * Performs JWT validation * * @param token token to verify * @throws SignatureException when signature is invalid * @throws JWTVerifyException when expiration, issuer or audience are invalid - * @throws IllegalStateException when token's structure is invalid + * @throws JWTAlgorithmException when the algorithm is missing or unsupported + * @throws IllegalStateException when token's structure is invalid or secret / public key does not match algorithm of token */ - public Map verify(String token) - throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, - IOException, SignatureException, JWTVerifyException { + public Map verify(final String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, + IOException, SignatureException, JWTVerifyException, JWTAlgorithmException { if (token == null || "".equals(token)) { throw new IllegalStateException("token not set"); } - - String[] pieces = token.split("\\."); - - // check number of segments + final String[] pieces = token.split("\\."); if (pieces.length != 3) { throw new IllegalStateException("Wrong number of segments: " + pieces.length); } - - // get JWTHeader JSON object. Extract algorithm - JsonNode jwtHeader = decodeAndParse(pieces[0]); - - String algorithm = getAlgorithm(jwtHeader); - - // get JWTClaims JSON object - JsonNode jwtPayload = decodeAndParse(pieces[1]); - - // check signature + final JsonNode jwtHeader = decodeAndParse(pieces[0]); + final Algorithm algorithm = getAlgorithm(jwtHeader); + final JsonNode jwtPayload = decodeAndParse(pieces[1]); verifySignature(pieces, algorithm); - - // additional JWTClaims checks verifyExpiration(jwtPayload); verifyIssuer(jwtPayload); verifyAudience(jwtPayload); - return mapper.treeToValue(jwtPayload, Map.class); } - void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - Mac hmac = Mac.getInstance(algorithm); - hmac.init(new SecretKeySpec(secret, algorithm)); - byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); + protected void verifySignature(final String[] pieces, final Algorithm algorithm) throws NoSuchAlgorithmException, + InvalidKeyException, SignatureException, JWTAlgorithmException, IllegalStateException { + Validate.notNull(pieces); + Validate.notNull(algorithm); + if (pieces.length != 3) { + throw new IllegalStateException("Wrong number of segments: " + pieces.length); + } + switch (algorithm) { + case HS256: + case HS384: + case HS512: + verifyHmac(algorithm, pieces, secret); + return; + case RS256: + case RS384: + case RS512: + verifyRs(algorithm, pieces, publicKey); + return; + default: + throw new JWTAlgorithmException("Unsupported signing method"); + } + } + void verifyHmac(final Algorithm algorithm, final String[] pieces, final byte[] secret) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + if (secret == null || secret.length == 0) { + throw new IllegalStateException("Secret cannot be null or empty when using algorithm: " + algorithm.getValue()); + } + final Mac hmac = Mac.getInstance(algorithm.getValue()); + hmac.init(new SecretKeySpec(secret, algorithm.getValue())); + final byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); if (!MessageDigest.isEqual(sig, decoder.decode(pieces[2]))) { throw new SignatureException("signature verification failed"); } } - void verifyExpiration(JsonNode jwtClaims) throws JWTExpiredException { - final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; + void verifyRs(final Algorithm algorithm, final String[] pieces, final PublicKey publicKey) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, JWTAlgorithmException { + if (publicKey == null) { + throw new IllegalStateException("PublicKey cannot be null when using algorithm: " + algorithm.getValue()); + } + final byte[] decodedSignatureBytes = new Base64(true).decode(pieces[2]); + final byte[] headerPayloadBytes = new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes(); + final boolean verified = verifySignatureWithPublicKey(this.publicKey, headerPayloadBytes, decodedSignatureBytes, algorithm); + if (!verified) { + throw new SignatureException("signature verification failed"); + } + } + + private boolean verifySignatureWithPublicKey(final PublicKey publicKey, final byte[] messageBytes, final byte[] signatureBytes, final Algorithm algorithm) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException, JWTAlgorithmException { + Validate.notNull(publicKey); + Validate.notNull(messageBytes); + Validate.notNull(signatureBytes); + Validate.notNull(algorithm); + try { + final Signature signature = Signature.getInstance(algorithm.getValue(), "BC"); + signature.initVerify(publicKey); + signature.update(messageBytes); + return signature.verify(signatureBytes); + } catch (NoSuchProviderException e) { + throw new JWTAlgorithmException(e.getMessage(), e.getCause()); + } + } + protected void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredException { + Validate.notNull(jwtClaims); + final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { throw new JWTExpiredException("jwt expired", expiration); } } - void verifyIssuer(JsonNode jwtClaims) throws JWTIssuerException { + protected void verifyIssuer(final JsonNode jwtClaims) throws JWTIssuerException { + Validate.notNull(jwtClaims); final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; - if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { throw new JWTIssuerException("jwt issuer invalid", issuerFromToken); } } - void verifyAudience(JsonNode jwtClaims) throws JWTAudienceException { + protected void verifyAudience(final JsonNode jwtClaims) throws JWTAudienceException { + Validate.notNull(jwtClaims); if (audience == null) return; - JsonNode audNode = jwtClaims.get("aud"); + final JsonNode audNode = jwtClaims.get("aud"); if (audNode == null) return; if (audNode.isArray()) { - for (JsonNode jsonNode : audNode) { + for (final JsonNode jsonNode : audNode) { if (audience.equals(jsonNode.textValue())) return; } @@ -155,23 +210,20 @@ void verifyAudience(JsonNode jwtClaims) throws JWTAudienceException { throw new JWTAudienceException("jwt audience invalid", audNode); } - String getAlgorithm(JsonNode jwtHeader) { + protected Algorithm getAlgorithm(final JsonNode jwtHeader) throws JWTAlgorithmException { + Validate.notNull(jwtHeader); final String algorithmName = jwtHeader.has("alg") ? jwtHeader.get("alg").asText() : null; - if (jwtHeader.get("alg") == null) { throw new IllegalStateException("algorithm not set"); } - - if (algorithms.get(algorithmName) == null) { - throw new IllegalStateException("unsupported algorithm"); - } - - return algorithms.get(algorithmName); + return Algorithm.findByName(algorithmName); } - JsonNode decodeAndParse(String b64String) throws IOException { - String jsonString = new String(decoder.decode(b64String), "UTF-8"); - JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); + protected JsonNode decodeAndParse(final String b64String) throws IOException { + Validate.notNull(b64String); + final String jsonString = new String(decoder.decode(b64String), "UTF-8"); + final JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } + } diff --git a/src/main/java/com/auth0/jwt/JWTVerifyException.java b/src/main/java/com/auth0/jwt/JWTVerifyException.java index 39ec2039..9e18d6b8 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifyException.java +++ b/src/main/java/com/auth0/jwt/JWTVerifyException.java @@ -1,22 +1,18 @@ package com.auth0.jwt; +/** + * Represents General Exception related to Verification + */ public class JWTVerifyException extends Exception { - /** - * - */ - private static final long serialVersionUID = -4911506451239107610L; - public JWTVerifyException() { - } - - + public JWTVerifyException() {} - public JWTVerifyException(String message, Throwable cause) { + public JWTVerifyException(final String message, final Throwable cause) { super(message, cause); } + public JWTVerifyException(final String message) { + super(message); + } - public JWTVerifyException(String message) { - super(message); - } } diff --git a/src/main/java/com/auth0/jwt/pem/PemFileReader.java b/src/main/java/com/auth0/jwt/pem/PemFileReader.java new file mode 100644 index 00000000..60eb9f33 --- /dev/null +++ b/src/main/java/com/auth0/jwt/pem/PemFileReader.java @@ -0,0 +1,38 @@ +package com.auth0.jwt.pem; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.*; + +/** + * Can read PEM from disk - depends on BouncyCastle PemReader + */ +class PemFileReader { + + private PemObject pemObject; + + public PemFileReader(final String filename) throws IOException { + final PemReader pemReader = new PemReader(new InputStreamReader(new FileInputStream(filename))); + try { + this.pemObject = pemReader.readPemObject(); + } finally { + pemReader.close(); + } + } + + public void write(final String filename) throws IOException { + final PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(filename))); + try { + pemWriter.writeObject(this.pemObject); + } finally { + pemWriter.close(); + } + } + + public PemObject getPemObject() { + return pemObject; + } + +} diff --git a/src/main/java/com/auth0/jwt/pem/PemFileWriter.java b/src/main/java/com/auth0/jwt/pem/PemFileWriter.java new file mode 100644 index 00000000..8b23df10 --- /dev/null +++ b/src/main/java/com/auth0/jwt/pem/PemFileWriter.java @@ -0,0 +1,31 @@ +package com.auth0.jwt.pem; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.security.Key; + +/** + * Can write PEM to disk - depends on BouncyCastle PemWriter + */ +class PemFileWriter { + + private PemObject pemObject; + + public PemFileWriter(final Key key, final String description) { + this.pemObject = new PemObject(description, key.getEncoded()); + } + + public void write(final String filename) throws IOException { + final PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(filename))); + try { + pemWriter.writeObject(this.pemObject); + } finally { + pemWriter.close(); + } + } + +} diff --git a/src/main/java/com/auth0/jwt/pem/PemReader.java b/src/main/java/com/auth0/jwt/pem/PemReader.java new file mode 100644 index 00000000..90ca0486 --- /dev/null +++ b/src/main/java/com/auth0/jwt/pem/PemReader.java @@ -0,0 +1,68 @@ +package com.auth0.jwt.pem; + +import org.apache.commons.lang3.Validate; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.security.*; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * Read operations for PEM files + */ +public class PemReader { + + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + public static PrivateKey readPrivateKey(final String filePath) throws NoSuchProviderException, NoSuchAlgorithmException, IOException, InvalidKeySpecException { + Validate.notNull(filePath); + final KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); + final PrivateKey privateKey = readPrivateKeyFromFile(factory, filePath); + return privateKey; + } + + public static PublicKey readPublicKey(final String filePath) throws NoSuchProviderException, NoSuchAlgorithmException, IOException, InvalidKeySpecException { + Validate.notNull(filePath); + final KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); + final PublicKey publicKey = readPublicKeyFromFile(factory, filePath); + return publicKey; + } + + private static PrivateKey readPrivateKeyFromFile(final KeyFactory factory, final String filename) throws InvalidKeySpecException, IOException { + Validate.notNull(factory); + Validate.notNull(filename); + final PemFileReader pemFileReader = new PemFileReader(filename); + final byte[] content = pemFileReader.getPemObject().getContent(); + final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); + return factory.generatePrivate(privKeySpec); + } + + private static PublicKey readPublicKeyFromFile(final KeyFactory factory, final String filename) throws InvalidKeySpecException, IOException { + Validate.notNull(factory); + Validate.notNull(filename); + final File file = new File(filename); + final byte[] data = Files.readAllBytes(file.toPath()); + final X509Certificate cert = X509CertUtils.parse(new String(data)); + if (cert != null) { + java.security.PublicKey publicKey = cert.getPublicKey(); + return publicKey; + } else { + final PemFileReader pemFileReader = new PemFileReader(filename); + final byte[] content = pemFileReader.getPemObject().getContent(); + final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content); + return factory.generatePublic(pubKeySpec); + } + + + } + +} diff --git a/src/main/java/com/auth0/jwt/pem/PemWriter.java b/src/main/java/com/auth0/jwt/pem/PemWriter.java new file mode 100644 index 00000000..55856ce4 --- /dev/null +++ b/src/main/java/com/auth0/jwt/pem/PemWriter.java @@ -0,0 +1,34 @@ +package com.auth0.jwt.pem; + +import org.apache.commons.lang3.Validate; + +import java.io.IOException; +import java.security.Key; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * Write operations for PEM files + */ +public class PemWriter { + + public static void writePrivateKey(final RSAPrivateKey privateKey, final String description, final String filename) throws IOException { + Validate.notNull(privateKey); + Validate.notNull(filename); + writePemFile(privateKey, description, filename); + } + + public static void writePublicKey(final RSAPublicKey publicKey, final String description, final String filename) throws IOException { + Validate.notNull(publicKey); + Validate.notNull(filename); + writePemFile(publicKey, description, filename); + } + + public static void writePemFile(final Key key, final String description, final String filename) throws IOException { + Validate.notNull(key); + Validate.notNull(filename); + final PemFileWriter pemFileWriter = new PemFileWriter(key, description); + pemFileWriter.write(filename); + } + +} diff --git a/src/main/java/com/auth0/jwt/pem/X509CertUtils.java b/src/main/java/com/auth0/jwt/pem/X509CertUtils.java new file mode 100644 index 00000000..ef65e476 --- /dev/null +++ b/src/main/java/com/auth0/jwt/pem/X509CertUtils.java @@ -0,0 +1,61 @@ +package com.auth0.jwt.pem; + +import org.apache.commons.codec.binary.Base64; + +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + + +/** + * X.509 certificate utilities. + */ +public class X509CertUtils { + + private static final String PEM_BEGIN_MARKER = "-----BEGIN CERTIFICATE-----"; + private static final String PEM_END_MARKER = "-----END CERTIFICATE-----"; + + /** + * Parses a DER-encoded X.509 certificate. + */ + public static X509Certificate parse(final byte[] derEncodedCert) { + if (derEncodedCert == null || derEncodedCert.length == 0) { + return null; + } + try { + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + final Certificate cert = cf.generateCertificate(new ByteArrayInputStream(derEncodedCert)); + if (!(cert instanceof X509Certificate)) { + return null; + } + return (X509Certificate) cert; + } catch (CertificateException e) { + return null; + } + } + + /** + * Parses a PEM-encoded X.509 certificate. + */ + public static X509Certificate parse(final String pemEncodedCert) { + if (pemEncodedCert == null || pemEncodedCert.isEmpty()) { + return null; + } + final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER); + if (markerStart < 0) { + return null; + } + String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length()); + final int markerEnd = buf.indexOf(PEM_END_MARKER); + if (markerEnd < 0) { + return null; + } + buf = buf.substring(0, markerEnd); + buf = buf.replaceAll("\\s", ""); + return parse(new Base64(true).decodeBase64(buf)); + } + +} + diff --git a/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java b/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java new file mode 100644 index 00000000..2ea1af6e --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java @@ -0,0 +1,103 @@ +package com.auth0.jwt; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.auth0.jwt.pem.PemReader.readPrivateKey; +import static com.auth0.jwt.pem.PemReader.readPublicKey; +import static com.auth0.jwt.pem.PemWriter.writePrivateKey; +import static com.auth0.jwt.pem.PemWriter.writePublicKey; +import static junit.framework.TestCase.*; + +/** + * Test that generates KeyPair - writes PEM files (private and public) to disk, + * then reads in those PEM files (private and public) and uses them to Sign a JWT + * and subsequently verify its correctness - hence "RoundTrip" + */ +public class JWTRoundTripRsa256Test { + + private static final int KEY_SIZE = 2048; + + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private File privateKeyPem; + private File publicKeyPem; + + @Before + public void createPemFiles() throws NoSuchAlgorithmException, NoSuchProviderException, IOException { + // create key pair + final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC"); + generator.initialize(KEY_SIZE); + final KeyPair keyPair = generator.generateKeyPair(); + // write private key + final RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + privateKeyPem = File.createTempFile("id_rsa", ""); + writePrivateKey(privateKey, "RSA PRIVATE KEY", privateKeyPem.getAbsolutePath()); + // write public key + publicKeyPem = File.createTempFile("id_rsa", ".pub"); + final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + writePublicKey(publicKey, "RSA PUBLIC KEY", publicKeyPem.getAbsolutePath()); + } + + @Test + public void roundTripCreatingSignedTokenAndVerifyingUsingRs256Algo() throws NoSuchProviderException, NoSuchAlgorithmException, IOException, InvalidKeySpecException, SignatureException, InvalidKeyException, JWTAlgorithmException, JWTVerifyException { + // read pem files + final PrivateKey privateKey = readPrivateKey(privateKeyPem.getAbsolutePath()); + assertNotNull(privateKey); + final PublicKey publicKey = readPublicKey(publicKeyPem.getAbsolutePath()); + assertNotNull(publicKey); + // create and sign a JWT + final String issuer = "https://arcseldon.auth0.com/"; + final String clientId = "xGXMKfEdcOcacZEU7Uq1mgWOtpUxBlL4"; // this is the audience + final String name = "arcseldon"; + final String email = "arcseldon+test@gmail.com"; + final String subject = "auth0|576be978a93121cc48c7487d"; + final List roles = new ArrayList<>(); + roles.add("ROLE_ADMIN"); + final long iat = System.currentTimeMillis() / 1000l; + final long exp = iat + 3600L; + final HashMap claims = new HashMap<>(); + claims.put("name", name); + claims.put("email", email); + claims.put("email_verified", "true"); + claims.put("iss", issuer); + claims.put("roles", roles.toArray(new String[0])); + claims.put("sub", subject); + claims.put("aud", clientId); + claims.put("exp", exp); + claims.put("iat", iat); + final JWTSigner jwtSigner = new JWTSigner(privateKey); + final JWTSigner.Options options = new JWTSigner.Options(); + options.setAlgorithm(Algorithm.RS256); + final String token = jwtSigner.sign(claims, options); + assertNotNull(token); + final JWTVerifier jwtVerifier = new JWTVerifier(publicKey); + final Map verifiedClaims = jwtVerifier.verify(token); + assertEquals(name, verifiedClaims.get("name")); + assertEquals(email, verifiedClaims.get("email")); + assertEquals("true", verifiedClaims.get("email_verified")); + assertTrue(roles.equals((List) verifiedClaims.get("roles"))); + assertEquals(issuer, verifiedClaims.get("iss")); + assertEquals(subject, verifiedClaims.get("sub")); + assertEquals(clientId, verifiedClaims.get("aud")); + assertTrue(exp == (Integer) verifiedClaims.get("exp")); + assertTrue(iat == (Integer) verifiedClaims.get("iat")); + } + +} diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/JWTRoundTripTest.java similarity index 90% rename from src/test/java/com/auth0/jwt/RoundtripTest.java rename to src/test/java/com/auth0/jwt/JWTRoundTripTest.java index 35299337..3fca12a9 100644 --- a/src/test/java/com/auth0/jwt/RoundtripTest.java +++ b/src/test/java/com/auth0/jwt/JWTRoundTripTest.java @@ -1,21 +1,19 @@ package com.auth0.jwt; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Test; -import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; -import org.apache.commons.codec.binary.Base64; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Test things that are difficult using signer or verifier alone. In particular, setting * claims via Options produces output dependent on current time. * */ -public class RoundtripTest { +public class JWTRoundTripTest { private static final String SECRET; static { SECRET = "my secret"; @@ -132,6 +130,32 @@ public void shouldOptionsJti() throws Exception { assertEquals(decoded.size(), 1); assertEquals(((String) decoded.get("jti")).length(), 36); } + + + public static class User { + + private String username; + private String password; + + public User() { + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } } diff --git a/src/test/java/com/auth0/jwt/JWTSignerBytesTest.java b/src/test/java/com/auth0/jwt/JWTSignerBytesTest.java new file mode 100644 index 00000000..2d5d06a4 --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTSignerBytesTest.java @@ -0,0 +1,205 @@ +package com.auth0.jwt; + +import com.auth0.jwt.Algorithm; +import com.auth0.jwt.JWTSigner; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; + +import static org.junit.Assert.assertEquals; + +/** + * General library JwtSigner unit tests using byte constructor alternative for secret + */ +public class JWTSignerBytesTest { + private static JWTSigner signer = new JWTSigner(new byte[] { 109, 121, 32, 115, 101, 99, 114, 101, 116}); + + @Test + public void shouldSignEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignEmptyTwoParams() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); + } + + @Test + public void shouldSignStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", "http://foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); + } + + @Test + public void shouldSignStringOrURI3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", ""); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); + } + + @Test + public void shouldSignStringOrURICollection() throws Exception { + HashMap claims = new HashMap(); + LinkedList aud = new LinkedList(); + aud.add("xyz"); + aud.add("ftp://foo"); + claims.put("aud", aud); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); + } + + @Test + public void shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void bytes_shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignIntDate3() throws Exception { + HashMap claims = new HashMap(); + claims.put("iat", Long.MAX_VALUE); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); + } + + @Test + public void bytes_shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); + } + + @Test + public void shouldSignNullEqualsMissing() throws Exception { + HashMap claims = new HashMap(); + for (String claimName : Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")) { + claims.put(claimName, null); + } + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", ":"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection1() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection2() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(0)); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(":")); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", -1); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", "100"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", 100); + signer.sign(claims); + } + + @Test + public void shouldOptionsNone() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, new JWTSigner.Options()); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldOptionsAll() throws Exception { + HashMap claims = new HashMap(); + signer.sign(claims, new JWTSigner.Options() + .setExpirySeconds(1000).setNotValidBeforeLeeway(5) + .setIssuedAt(true).setJwtId(true)); + } + + @Test + public void shouldOptionsAlgorithm() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.11MgCe-_uiheyy_kARCwhSZbeq3IkMn40GLQkczQ4Bjn_lkCYfSeqz0HeeYpitksiQ2bW47N0oGKCOYOlmQPyg", token); + } + +} diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest.java b/src/test/java/com/auth0/jwt/JWTSignerTest.java index a63f52d3..099e5c12 100644 --- a/src/test/java/com/auth0/jwt/JWTSignerTest.java +++ b/src/test/java/com/auth0/jwt/JWTSignerTest.java @@ -1,17 +1,20 @@ package com.auth0.jwt; -import static org.junit.Assert.assertEquals; +import org.junit.Test; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; -import java.util.List; -import org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * General library JwtSigner related unit tests + */ public class JWTSignerTest { private static JWTSigner signer = new JWTSigner("my secret"); - + @Test public void shouldSignEmpty() throws Exception { HashMap claims = new HashMap(); @@ -25,7 +28,7 @@ public void shouldSignEmptyTwoParams() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } - + @Test public void shouldSignStringOrURI1() throws Exception { HashMap claims = new HashMap(); @@ -33,7 +36,7 @@ public void shouldSignStringOrURI1() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); } - + @Test public void shouldSignStringOrURI2() throws Exception { HashMap claims = new HashMap(); @@ -41,7 +44,7 @@ public void shouldSignStringOrURI2() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); } - + @Test public void shouldSignStringOrURI3() throws Exception { HashMap claims = new HashMap(); @@ -49,7 +52,7 @@ public void shouldSignStringOrURI3() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); } - + @Test public void shouldSignStringOrURICollection() throws Exception { HashMap claims = new HashMap(); @@ -60,7 +63,7 @@ public void shouldSignStringOrURICollection() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); } - + @Test public void shouldSignIntDate1() throws Exception { HashMap claims = new HashMap(); @@ -76,7 +79,7 @@ public void shouldSignIntDate2() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); } - + @Test public void shouldSignIntDate3() throws Exception { HashMap claims = new HashMap(); @@ -84,7 +87,7 @@ public void shouldSignIntDate3() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); } - + @Test public void shouldSignString() throws Exception { HashMap claims = new HashMap(); @@ -92,7 +95,7 @@ public void shouldSignString() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); } - + @Test public void shouldSignNullEqualsMissing() throws Exception { HashMap claims = new HashMap(); @@ -102,70 +105,70 @@ public void shouldSignNullEqualsMissing() throws Exception { String token = signer.sign(claims); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } - + @Test(expected = Exception.class) public void shouldFailExpectStringOrURI1() throws Exception { HashMap claims = new HashMap(); claims.put("iss", 0); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectStringOrURI2() throws Exception { HashMap claims = new HashMap(); claims.put("sub", ":"); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectStringOrURICollection1() throws Exception { HashMap claims = new HashMap(); claims.put("aud", 0); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectStringOrURICollection2() throws Exception { HashMap claims = new HashMap(); claims.put("aud", Arrays.asList(0)); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectStringOrURICollection3() throws Exception { HashMap claims = new HashMap(); claims.put("aud", Arrays.asList(":")); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectIntDate1() throws Exception { HashMap claims = new HashMap(); claims.put("exp", -1); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectIntDate2() throws Exception { HashMap claims = new HashMap(); claims.put("nbf", "100"); signer.sign(claims); } - + @Test(expected = Exception.class) public void shouldFailExpectString() throws Exception { HashMap claims = new HashMap(); claims.put("jti", 100); signer.sign(claims); } - + @Test public void shouldOptionsNone() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims, new JWTSigner.Options()); assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } - + @Test public void shouldOptionsAll() throws Exception { HashMap claims = new HashMap(); diff --git a/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java b/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java new file mode 100644 index 00000000..a4c1b86e --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java @@ -0,0 +1,85 @@ +package com.auth0.jwt; + +import org.junit.Test; + +import java.security.PublicKey; +import java.security.SignatureException; + +import static com.auth0.jwt.pem.PemReader.readPublicKey; +import static junit.framework.TestCase.assertNotNull; + +/** + * RS256 Verification Checks + */ +public class JWTVerifierRsa256Test { + + public final static String RESOURCES_DIR = "src/test/resources/auth0-pem/"; + public final static String MISMATCHED_RESOURCES_DIR = "src/test/resources/test-pem/"; + public final static String PUBLIC_KEY_PEM_FILENAME = "key.pem"; + public final static String MISMATCHED_PUBLIC_KEY_PEM_FILENAME = "test-auth0.pem"; + + + + /** + * Here we pass in a public key that does not correspond to the private key that was used to sign the JWT Token + */ + @Test(expected = SignatureException.class) + public void shouldFailOnInvalidSignature() throws Exception { + final String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFVTkVRelZCTXpoRk1FVTNRMFZGTURJNFFqYzROakJDTkRSQ1JFRkNSalkzUWpnMFJEVXlOZyJ9" + + "." + + "eyJyb2xlcyI6WyJST0xFX0FETUlOIl0sInVzZXJfaWQiOiJhdXRoMHw1NzcxMGU5ZDE0MWIwN2YyMmU3NDNhYzciLCJlbWFpbCI6ImFyY3NlbGRvbit0cm5AZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYWppbG9uMS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NTc3MTBlOWQxNDFiMDdmMjJlNzQzYWM3IiwiYXVkIjoibm5ld1NobHBHVDFBbkZpY1ExUGlKWXdheENuejE4eUIiLCJleHAiOjE0Njc3MDk5OTMsImlhdCI6MTQ2NzY3Mzk5M30" + + "." + + "gQML78V8H6WN3MSN1QhrFG4AxNTdFChPBQxrnuqPF0iBvf35v_z9oDzTERaPBDWFHzWT17h0ADxpl7tCIo43k0FoFie6RHa5j82iHnOKPhcqM5hArfKDYk3G5gc30lVmFiMm8PX8WKzDExygLqXZVnIzfB-EmcJWW_2fLiFEMpNC8KDTBVAiyds_n5kiGmW6F_QpLt11af3BDy71tg2fuqkyJE6pEHd1HsTHNCFQzWt7GevVB0HouJS099p6GphsH3kIhmAvHp5j267uYv49sndiUaLq7bL6GZnzv8dhzgQlucHvNaIZ6m6m6n4t43cjUxSrO0ZP9Crv9NBDJme0cA"; + final PublicKey publicKey = readPublicKey(MISMATCHED_RESOURCES_DIR + MISMATCHED_PUBLIC_KEY_PEM_FILENAME); + assertNotNull(publicKey); + new JWTVerifier(publicKey, "audience").verifySignature(token.split("\\."), Algorithm.RS256); + } + + /** + * Here we pass in a public key that correctly corresponds to the private key that was used to sign the JWT Token + */ + @Test + public void shouldVerifySignature() throws Exception { + final String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFVTkVRelZCTXpoRk1FVTNRMFZGTURJNFFqYzROakJDTkRSQ1JFRkNSalkzUWpnMFJEVXlOZyJ9" + + "." + + "eyJyb2xlcyI6WyJST0xFX0FETUlOIl0sInVzZXJfaWQiOiJhdXRoMHw1NzcxMGU5ZDE0MWIwN2YyMmU3NDNhYzciLCJlbWFpbCI6ImFyY3NlbGRvbit0cm5AZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYWppbG9uMS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NTc3MTBlOWQxNDFiMDdmMjJlNzQzYWM3IiwiYXVkIjoibm5ld1NobHBHVDFBbkZpY1ExUGlKWXdheENuejE4eUIiLCJleHAiOjE0Njc3MDk5OTMsImlhdCI6MTQ2NzY3Mzk5M30" + + "." + + "gQML78V8H6WN3MSN1QhrFG4AxNTdFChPBQxrnuqPF0iBvf35v_z9oDzTERaPBDWFHzWT17h0ADxpl7tCIo43k0FoFie6RHa5j82iHnOKPhcqM5hArfKDYk3G5gc30lVmFiMm8PX8WKzDExygLqXZVnIzfB-EmcJWW_2fLiFEMpNC8KDTBVAiyds_n5kiGmW6F_QpLt11af3BDy71tg2fuqkyJE6pEHd1HsTHNCFQzWt7GevVB0HouJS099p6GphsH3kIhmAvHp5j267uYv49sndiUaLq7bL6GZnzv8dhzgQlucHvNaIZ6m6m6n4t43cjUxSrO0ZP9Crv9NBDJme0cA"; + final PublicKey publicKey = readPublicKey(RESOURCES_DIR + PUBLIC_KEY_PEM_FILENAME); + assertNotNull(publicKey); + new JWTVerifier(publicKey, "audience").verifySignature(token.split("\\."), Algorithm.RS256); + } + + /** + * Here we modify the signature on an otherwise legal JWT Token and check verification using the correct Public Key fails + */ + @Test(expected = SignatureException.class) + public void shouldFailOnInvalidJWTTokenSignature() throws Exception { + final String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFVTkVRelZCTXpoRk1FVTNRMFZGTURJNFFqYzROakJDTkRSQ1JFRkNSalkzUWpnMFJEVXlOZyJ9" + + "." + + "eyJyb2xlcyI6WyJST0xFX0FETUlOIl0sInVzZXJfaWQiOiJhdXRoMHw1NzcxMGU5ZDE0MWIwN2YyMmU3NDNhYzciLCJlbWFpbCI6ImFyY3NlbGRvbit0cm5AZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYWppbG9uMS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NTc3MTBlOWQxNDFiMDdmMjJlNzQzYWM3IiwiYXVkIjoibm5ld1NobHBHVDFBbkZpY1ExUGlKWXdheENuejE4eUIiLCJleHAiOjE0Njc3MDk5OTMsImlhdCI6MTQ2NzY3Mzk5M30" + + "." + + "XXXXX8V8H6WN3MSN1QhrFG4AxNTdFChPBQxrnuqPF0iBvf35v_z9oDzTERaPBDWFHzWT17h0ADxpl7tCIo43k0FoFie6RHa5j82iHnOKPhcqM5hArfKDYk3G5gc30lVmFiMm8PX8WKzDExygLqXZVnIzfB-EmcJWW_2fLiFEMpNC8KDTBVAiyds_n5kiGmW6F_QpLt11af3BDy71tg2fuqkyJE6pEHd1HsTHNCFQzWt7GevVB0HouJS099p6GphsH3kIhmAvHp5j267uYv49sndiUaLq7bL6GZnzv8dhzgQlucHvNaIZ6m6m6n4t43cjUxSrO0ZP9Crv9NBDJme0cA"; + final PublicKey publicKey = readPublicKey(RESOURCES_DIR + PUBLIC_KEY_PEM_FILENAME); + assertNotNull(publicKey); + new JWTVerifier(publicKey, "audience").verifySignature(token.split("\\."), Algorithm.RS256); + } + + /** + * Here we modify the payload section on an otherwise legal JWT Token and check verification using the correct Public Key and + * unaltered JWT signnature (which now doesn't match the payload) fails + */ + @Test(expected = SignatureException.class) + public void shouldFailOnInvalidJWTTokenPayload() throws Exception { + final String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFVTkVRelZCTXpoRk1FVTNRMFZGTURJNFFqYzROakJDTkRSQ1JFRkNSalkzUWpnMFJEVXlOZyJ9" + + "." + + "XXXXX2xlcyI6WyJST0xFX0FETUlOIl0sInVzZXJfaWQiOiJhdXRoMHw1NzcxMGU5ZDE0MWIwN2YyMmU3NDNhYzciLCJlbWFpbCI6ImFyY3NlbGRvbit0cm5AZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYWppbG9uMS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NTc3MTBlOWQxNDFiMDdmMjJlNzQzYWM3IiwiYXVkIjoibm5ld1NobHBHVDFBbkZpY1ExUGlKWXdheENuejE4eUIiLCJleHAiOjE0Njc3MDk5OTMsImlhdCI6MTQ2NzY3Mzk5M30" + + "." + + "gQML78V8H6WN3MSN1QhrFG4AxNTdFChPBQxrnuqPF0iBvf35v_z9oDzTERaPBDWFHzWT17h0ADxpl7tCIo43k0FoFie6RHa5j82iHnOKPhcqM5hArfKDYk3G5gc30lVmFiMm8PX8WKzDExygLqXZVnIzfB-EmcJWW_2fLiFEMpNC8KDTBVAiyds_n5kiGmW6F_QpLt11af3BDy71tg2fuqkyJE6pEHd1HsTHNCFQzWt7GevVB0HouJS099p6GphsH3kIhmAvHp5j267uYv49sndiUaLq7bL6GZnzv8dhzgQlucHvNaIZ6m6m6n4t43cjUxSrO0ZP9Crv9NBDJme0cA"; + final PublicKey publicKey = readPublicKey(RESOURCES_DIR + PUBLIC_KEY_PEM_FILENAME); + assertNotNull(publicKey); + new JWTVerifier(publicKey, "audience").verifySignature(token.split("\\."), Algorithm.RS256); + } + +} + diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 7cdc4cd4..db096106 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; - import org.apache.commons.codec.binary.Base64; import org.junit.Test; @@ -13,12 +12,15 @@ import static org.junit.Assert.assertEquals; +/** + * General library JWTVerifier related unit tests + */ public class JWTVerifierTest { - - private static final Base64 decoder = new Base64(true);; - - @Test(expected = IllegalArgumentException.class) + private static final Base64 decoder = new Base64(true); + + + @Test(expected = IllegalArgumentException.class) public void constructorShouldFailOnEmptySecret() { new JWTVerifier(""); } @@ -53,14 +55,14 @@ public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { new JWTVerifier("such secret").getAlgorithm(JsonNodeFactory.instance.objectNode()); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTAlgorithmException.class) public void shouldFailIfAlgorithmIsNotSupported() throws Exception { new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "doge-crypt")); } @Test public void shouldWorkIfAlgorithmIsSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "HS256")); + new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "HS256")); } @Test(expected = SignatureException.class) @@ -72,7 +74,7 @@ public void shouldFailOnInvalidSignature() throws Exception { "." + "suchsignature_plzvalidate_zomgtokens"; String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; - new JWTVerifier(secret, "audience").verifySignature(jws.split("\\."), "HmacSHA256"); + new JWTVerifier(secret, "audience").verifySignature(jws.split("\\."), Algorithm.HS256); } @Test @@ -85,7 +87,7 @@ public void shouldVerifySignature() throws Exception { "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; byte[] secret = decoder.decodeBase64("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); new JWTVerifier(secret, "audience") - .verifySignature(jws.split("\\."), "HmacSHA256"); + .verifySignature(jws.split("\\."), Algorithm.HS256); } @Test(expected = JWTExpiredException.class) @@ -148,14 +150,14 @@ public void shouldVerifyArrayAudience() throws Exception { .verifyAudience(createSingletonJSONNode("aud", new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); } - + @Test(expected = JWTAudienceException.class) public void shouldFailArrayAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") .verifyAudience(createSingletonJSONNode("aud", new ObjectMapper().readValue("[ \"foo\" ]", ArrayNode.class))); } - + @Test public void decodeAndParse() throws Exception { final Base64 encoder = new Base64(true); diff --git a/src/test/java/com/auth0/jwt/User.java b/src/test/java/com/auth0/jwt/User.java deleted file mode 100644 index dc50b2b5..00000000 --- a/src/test/java/com/auth0/jwt/User.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.auth0.jwt; - -/** - * Sample object for serialization - */ -public class User { - - private String username; - private String password; - - public User() { - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/src/test/resources/auth0-pem/key.pem b/src/test/resources/auth0-pem/key.pem new file mode 100644 index 00000000..ce12c0ab --- /dev/null +++ b/src/test/resources/auth0-pem/key.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdKgAwIBAgIJRJ6BX2ejIsNmMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNV +BAMTEWFqaWxvbjEuYXV0aDAuY29tMB4XDTE2MDYxNDA0MjUxMloXDTMwMDIyMTA0 +MjUxMlowHDEaMBgGA1UEAxMRYWppbG9uMS5hdXRoMC5jb20wggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDWP321jJBlY521B3JqLC7RxFdsIQdDCViav2Z+ +dIOWyN09LxGrAy7pmV8GIHeu5lrmrfzUTfV+MzMyF+9UzRvq79qmMEWfyHBRNCOs +Ig3Sw0DHm3OoyCpdXBAF5F+IclPe2r2Bey7zlXkTUIZ+q4qLuDNKFiONyZdhkBUa +C1TsuplTv7DF2IHwnQpI4MyvRghy8QvxbqMZOj39mBrQYMLCWz2/PXIYRWiu3lxx +WSZS4GJya/2mTZtIatacz1t1W6dLeoS+OuTsPWl4qHsLj14EYPOevaw+8Nzh4eAa +tFRP2VOSm1nqDipuLIRnERSHEF4B6YYiG9EI8E3KrogoeGovAgMBAAGjLzAtMAwG +A1UdEwQFMAMBAf8wHQYDVR0OBBYEFK1l4Why6NUQ8g/7cdIbFtSbBYYjMA0GCSqG +SIb3DQEBBQUAA4IBAQAH+pc6gt5Hyjmqe6GxMRuDqd6/rX8c0EN0XmZ4wDfgbN7l +tN3iQ6i3R5I66GuohfxHFxxCYBnlQcZPFJ4/ZcfgtK42md0dx9Lt1se7E12B32mc +WISWs/dtrKLF6x+kNXJj0j05C0fMBuuxaqmTWJuJZ7LOJG3VS/jLR8t9TIdWexzL +xIDrf8MYjR9D+phLOJmf0ZZMbfxpCb4+txVs+yMAqhhiosXmjluLEIo2X5fejC92 +GVNY+Dm7S1ecWvYwzvx6R8VNqbAZ0QbVU/1dTPff57dfeMlpiXdGf6vc7OLgs8re +Om1fjmXhB9X6G+RYkaReI2VYkfU7ZRIQ/dI9HnYN +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/test/resources/test-pem/test-auth0.key b/src/test/resources/test-pem/test-auth0.key new file mode 100644 index 00000000..663a7374 --- /dev/null +++ b/src/test/resources/test-pem/test-auth0.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxmJWY0eJcuV2uBtLnQ4004fuknbODo5xIyRhkYNkls5n9OrB +q4Lok6cjv7G2Q8mxAdlIUmzhTSyuNkrMMKZrPaMsAkNKE/aNpeWuSLXqcMs8T/8g +YCDcEmC5KYEJakNtKb3ZX2FKwT4yHHpsNomLDzJD5DyJKbRpNBm2no7ggIy7TQRJ +2H00mogQIQu8/fUANXVeGPshvLJU8MXEy/eiXkHJIT3DDA4VSr/C/tfP0tGJSNTM +874urc4zej+4INuTuMPtesZS47J0AsPxQuxengS4M76cVt5cH+Iqd1nKe5UqiSKv +LCXacPYg/T/Kdx0tBnwHIjKo/cbzZ+r+XynsCwIDAQABAoIBAFPWWwu5v6x+rJ1B +a8MDre93Eqty6cHdEJL5XQJRtMDGmcg3LYF94SwFBmaMg6pCIjvVx2qN+OjUaQso +sQIeUlPKEV8jcLrfBx2E4xJ3Tow8V1C3UMdPG7Hojler4H633/oz8RkN1Lm1vxep +5PFnTw0tAOQDcTPeulb6RuLbHqU0FEnf/jVOMhtPLcMAwJ3fkAJQ+ljFW2VKCQ83 +d+ci1p+NHY/dbGLSR4lK58mVghcRMO3zhe5scrbECHJMfT6fCb2TXdjaueFUGC6+ +fqUXvDj8HRfUilzTegNq8ZhwgMSw1HeX/PuiczSKc3aHYSsohMBugTErnkW+qF4Z +kE+kxgECgYEA/sm7umcyFuZME+RWYL8Gsp8agH1OGEgsmIiMi1z6RTlTmdR8fN18 +ItzXyW+363VZln/1b5wCaPdLIxgASxybLAaxnKAXfmL7QvyVAaMwxj7N0ogvMQoN +x2VuSGZSam2+LFVIMWHq1C+3fvVnCDLm6oHvIMK/zvEsPBBtz+L6rlECgYEAx1Pr +KogaGHCi1XgsrNv9aFaayRvmhzZbmiigF0iWKAd3KKww94BdyyGSVfMfyL23LAbM +QDCrDNGpYAnpNZo/cL+OcGPYzlPsWDBrJub1HOA/H3WQlP4oEcfdbmJZhIkEwTGF +HaCHynEu4ekiCrWz9+XVNCquTyqnmaVDEzAfEZsCgYA8jQbfUt0Vkh+sboyUq3FV +C/jJZn4jyStICNOV3z/fKbOTkGsRZbW1t1RVHAbSn23uFXTn1GTCO1sQ+QhA0YiT +Gvgk5+sNb0qVbd+fpv/VbWGO0iyc8+24YIOoEyEtB+21LYNdsQ6U5M4wDvQwf6Bf +RQfmekIJVUmU8LaYPDIlMQKBgDSRiT/aTSeM7STnYMDl89sEnCXV2eJnD5mEhVQe +rJs5/M8ZOoDLtfDQlctdJ1DF1/0gfdWgADyNPuI5OuwMFhciLequKoufzoEjo97K +onJPIdamJs9kiCTIVTm7bmhpyns5GCZMJAPb/cVOus+gRCpozuXHK9ltIm5/C0WQ +N2FpAoGBAOss6RN2krieqbn1mG8e2v5mMUd0CJkiJu2y5MnF3dYHXSQ3/ePAh/Yg +JOthpgYgBh+mV0DLqJhx/1DLS/xiqcoHDlndQDmYbtvvY7RlMo00+nGzkRVOfrqy +hC+1KsYHGPbSQixNQXtvFbAAVMSo+RRBkVGINYGDFnlQUpkppYRk +-----END RSA PRIVATE KEY----- diff --git a/src/test/resources/test-pem/test-auth0.pem b/src/test/resources/test-pem/test-auth0.pem new file mode 100644 index 00000000..47e8e146 --- /dev/null +++ b/src/test/resources/test-pem/test-auth0.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIJALr9HwgrQ7GeMA0GCSqGSIb3DQEBBQUAMGIxGDAWBgNV +BAMTD2F1dGgwLmF1dGgwLmNvbTESMBAGA1UEChMJQXV0aDAgTExDMQswCQYDVQQG +EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDAeFw0x +MjEyMjkxNTMwNDdaFw0xMzAxMjgxNTMwNDdaMGIxGDAWBgNVBAMTD2F1dGgwLmF1 +dGgwLmNvbTESMBAGA1UEChMJQXV0aDAgTExDMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMZiVmNHiXLldrgbS50ONNOH7pJ2zg6OcSMkYZGDZJbO +Z/TqwauC6JOnI7+xtkPJsQHZSFJs4U0srjZKzDCmaz2jLAJDShP2jaXlrki16nDL +PE//IGAg3BJguSmBCWpDbSm92V9hSsE+Mhx6bDaJiw8yQ+Q8iSm0aTQZtp6O4ICM +u00ESdh9NJqIECELvP31ADV1Xhj7IbyyVPDFxMv3ol5BySE9wwwOFUq/wv7Xz9LR +iUjUzPO+Lq3OM3o/uCDbk7jD7XrGUuOydALD8ULsXp4EuDO+nFbeXB/iKndZynuV +Kokirywl2nD2IP0/yncdLQZ8ByIyqP3G82fq/l8p7AsCAwEAAaOBxzCBxDAdBgNV +HQ4EFgQUHI2rUXeBjTv1zAllaPGrHFcEK0YwgZQGA1UdIwSBjDCBiYAUHI2rUXeB +jTv1zAllaPGrHFcEK0ahZqRkMGIxGDAWBgNVBAMTD2F1dGgwLmF1dGgwLmNvbTES +MBAGA1UEChMJQXV0aDAgTExDMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZIIJALr9HwgrQ7GeMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAFrXIhCy4T4eGrikb0R2wHv/uS548r3pZyBV0CDb +cRwAtbnpJMvkGFqKVp4pmyoIDSVNK/j+sLEshB20XftezHZyRJbCUbtKvXQ6Fsxo +eZMlN0ITYKTaoBZKhUxxj90otAhNC58qwGUPqt2LewJhHyLucKkGJ1mQ3b5xKZ53 +2ToufouH9VLhig3H1KnxWo/zMD6Ke8cCk6qO9htuhI06s3GQGS1QWQtAmm17C6Tf +KgDwQFZwhqHUUZnwKRH8gU6OgZsvhgV1B7H5mjZcu57KMiDBekU9MEY0DCVTN3Wk +mcTII668zLsJrkNX6PEfck1AMBbVE6pEUKcWwq3uaLvlAUo= +-----END CERTIFICATE----- From 9c8132df195b55c4eea91432edfd0bfcbbdd6183 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 12 Jul 2016 18:10:09 -0300 Subject: [PATCH 71/92] Properly validate issuer & audience when supplied Previously JWTVerifier didn't validate issuer & audience if those claims where missing from the token, deeming it valid. --- .../com/auth0/jwt/JWTAudienceException.java | 2 -- .../com/auth0/jwt/JWTIssuerException.java | 4 ---- src/main/java/com/auth0/jwt/JWTVerifier.java | 22 ++++++++++++++----- .../java/com/auth0/jwt/JWTVerifierTest.java | 21 +++++++++++++++++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTAudienceException.java b/src/main/java/com/auth0/jwt/JWTAudienceException.java index dc6192a0..622d73b7 100644 --- a/src/main/java/com/auth0/jwt/JWTAudienceException.java +++ b/src/main/java/com/auth0/jwt/JWTAudienceException.java @@ -14,13 +14,11 @@ public class JWTAudienceException extends JWTVerifyException { private JsonNode audienceNode; public JWTAudienceException(final JsonNode audienceNode) { - Validate.notNull(audienceNode); this.audienceNode = audienceNode; } public JWTAudienceException(final String message, final JsonNode audienceNode) { super(message); - Validate.notNull(audienceNode); this.audienceNode = audienceNode; } diff --git a/src/main/java/com/auth0/jwt/JWTIssuerException.java b/src/main/java/com/auth0/jwt/JWTIssuerException.java index 7f0b1cd4..13463ddc 100644 --- a/src/main/java/com/auth0/jwt/JWTIssuerException.java +++ b/src/main/java/com/auth0/jwt/JWTIssuerException.java @@ -1,7 +1,5 @@ package com.auth0.jwt; -import org.apache.commons.lang3.Validate; - /** * Represents Exception related to Issuer - for example issuer mismatch / missing upon verification */ @@ -10,13 +8,11 @@ public class JWTIssuerException extends JWTVerifyException { private final String issuer; public JWTIssuerException(final String issuer) { - Validate.notNull(issuer); this.issuer = issuer; } public JWTIssuerException(final String message, final String issuer) { super(message); - Validate.notNull(issuer); this.issuer = issuer; } diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 9696a831..7f456ac5 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -185,27 +185,37 @@ protected void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredExcep protected void verifyIssuer(final JsonNode jwtClaims) throws JWTIssuerException { Validate.notNull(jwtClaims); + + if (this.issuer == null ) { + return; + } + final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; - if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { + + if (issuerFromToken == null || !issuer.equals(issuerFromToken)) { throw new JWTIssuerException("jwt issuer invalid", issuerFromToken); } } protected void verifyAudience(final JsonNode jwtClaims) throws JWTAudienceException { Validate.notNull(jwtClaims); - if (audience == null) + if (audience == null) { return; + } final JsonNode audNode = jwtClaims.get("aud"); - if (audNode == null) - return; + if (audNode == null) { + throw new JWTAudienceException("jwt audience invalid", null); + } if (audNode.isArray()) { for (final JsonNode jsonNode : audNode) { - if (audience.equals(jsonNode.textValue())) + if (audience.equals(jsonNode.textValue())) { return; + } } } else if (audNode.isTextual()) { - if (audience.equals(audNode.textValue())) + if (audience.equals(audNode.textValue())) { return; + } } throw new JWTAudienceException("jwt audience invalid", audNode); } diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index db096106..8529929c 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -6,7 +6,9 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.codec.binary.Base64; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.security.SignatureException; @@ -19,6 +21,8 @@ public class JWTVerifierTest { private static final Base64 decoder = new Base64(true); + @Rule + public ExpectedException expectedException = ExpectedException.none(); @Test(expected = IllegalArgumentException.class) public void constructorShouldFailOnEmptySecret() { @@ -85,7 +89,7 @@ public void shouldVerifySignature() throws Exception { "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + "." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - byte[] secret = decoder.decodeBase64("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); + byte[] secret = decoder.decode("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); new JWTVerifier(secret, "audience") .verifySignature(jws.split("\\."), Algorithm.HS256); } @@ -116,6 +120,7 @@ public void shouldFailIssuer() throws Exception { @Test public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { + expectedException.expect(JWTIssuerException.class); new JWTVerifier("such secret", "amaze audience", "very issuer") .verifyIssuer(JsonNodeFactory.instance.objectNode()); } @@ -134,6 +139,7 @@ public void shouldFailAudience() throws Exception { @Test public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { + expectedException.expect(JWTAudienceException.class); new JWTVerifier("such secret", "amaze audience") .verifyAudience(JsonNodeFactory.instance.objectNode()); } @@ -171,6 +177,19 @@ public void decodeAndParse() throws Exception { assertEquals("123", decodedJSON.get("number").asText()); } + @Test + public void shouldVerifyAudienceFromToken() throws Exception { + expectedException.expect(JWTAudienceException.class); + JWTVerifier verifier = new JWTVerifier("I.O.U a secret", "samples-api", null); + verifier.verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.wLlz9xDltxqKHQC7BeauPi5Q4KQK4nDjlRqQPvKVLYk"); + } + + @Test + public void shouldVerifyIssuerFromToken() throws Exception { + expectedException.expect(JWTIssuerException.class); + JWTVerifier verifier = new JWTVerifier("I.O.U a secret", null, "samples.auth0.com"); + verifier.verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.wLlz9xDltxqKHQC7BeauPi5Q4KQK4nDjlRqQPvKVLYk"); + } public static JsonNode createSingletonJSONNode(String key, String value) { final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); From 673200626c5e495efd008662795f1fd36e715776 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 12 Jul 2016 18:38:34 -0300 Subject: [PATCH 72/92] Refactor tests & Update to JUnit 4.12 --- .travis.yml | 6 + pom.xml | 2 +- src/main/java/com/auth0/jwt/JWTVerifier.java | 26 ++--- .../java/com/auth0/jwt/JWTVerifierTest.java | 105 ++++++++++++------ 4 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e20d23ce --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: java +jdk: +- oraclejdk7 +branches: + only: + - master diff --git a/pom.xml b/pom.xml index a4089c0c..aa06a2d8 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ junit junit - 4.11 + 4.12 test diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 7f456ac5..0e0199fa 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -94,8 +94,9 @@ public JWTVerifier(final PublicKey publicKey) { * @throws JWTAlgorithmException when the algorithm is missing or unsupported * @throws IllegalStateException when token's structure is invalid or secret / public key does not match algorithm of token */ + @SuppressWarnings("WeakerAccess") public Map verify(final String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, - IOException, SignatureException, JWTVerifyException, JWTAlgorithmException { + IOException, SignatureException, JWTVerifyException { if (token == null || "".equals(token)) { throw new IllegalStateException("token not set"); } @@ -113,7 +114,7 @@ public Map verify(final String token) throws NoSuchAlgorithmExce return mapper.treeToValue(jwtPayload, Map.class); } - protected void verifySignature(final String[] pieces, final Algorithm algorithm) throws NoSuchAlgorithmException, + void verifySignature(final String[] pieces, final Algorithm algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, JWTAlgorithmException, IllegalStateException { Validate.notNull(pieces); Validate.notNull(algorithm); @@ -136,24 +137,24 @@ protected void verifySignature(final String[] pieces, final Algorithm algorithm) } } - void verifyHmac(final Algorithm algorithm, final String[] pieces, final byte[] secret) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + private void verifyHmac(final Algorithm algorithm, final String[] pieces, final byte[] secret) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { if (secret == null || secret.length == 0) { throw new IllegalStateException("Secret cannot be null or empty when using algorithm: " + algorithm.getValue()); } final Mac hmac = Mac.getInstance(algorithm.getValue()); hmac.init(new SecretKeySpec(secret, algorithm.getValue())); - final byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); + final byte[] sig = hmac.doFinal((pieces[0] + "." + pieces[1]).getBytes()); if (!MessageDigest.isEqual(sig, decoder.decode(pieces[2]))) { throw new SignatureException("signature verification failed"); } } - void verifyRs(final Algorithm algorithm, final String[] pieces, final PublicKey publicKey) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, JWTAlgorithmException { + private void verifyRs(final Algorithm algorithm, final String[] pieces, final PublicKey publicKey) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, JWTAlgorithmException { if (publicKey == null) { throw new IllegalStateException("PublicKey cannot be null when using algorithm: " + algorithm.getValue()); } final byte[] decodedSignatureBytes = new Base64(true).decode(pieces[2]); - final byte[] headerPayloadBytes = new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes(); + final byte[] headerPayloadBytes = (pieces[0] + "." + pieces[1]).getBytes(); final boolean verified = verifySignatureWithPublicKey(this.publicKey, headerPayloadBytes, decodedSignatureBytes, algorithm); if (!verified) { throw new SignatureException("signature verification failed"); @@ -175,7 +176,7 @@ private boolean verifySignatureWithPublicKey(final PublicKey publicKey, final by } } - protected void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredException { + void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredException { Validate.notNull(jwtClaims); final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { @@ -183,7 +184,7 @@ protected void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredExcep } } - protected void verifyIssuer(final JsonNode jwtClaims) throws JWTIssuerException { + void verifyIssuer(final JsonNode jwtClaims) throws JWTIssuerException { Validate.notNull(jwtClaims); if (this.issuer == null ) { @@ -197,7 +198,7 @@ protected void verifyIssuer(final JsonNode jwtClaims) throws JWTIssuerException } } - protected void verifyAudience(final JsonNode jwtClaims) throws JWTAudienceException { + void verifyAudience(final JsonNode jwtClaims) throws JWTAudienceException { Validate.notNull(jwtClaims); if (audience == null) { return; @@ -220,7 +221,7 @@ protected void verifyAudience(final JsonNode jwtClaims) throws JWTAudienceExcept throw new JWTAudienceException("jwt audience invalid", audNode); } - protected Algorithm getAlgorithm(final JsonNode jwtHeader) throws JWTAlgorithmException { + Algorithm getAlgorithm(final JsonNode jwtHeader) throws JWTAlgorithmException { Validate.notNull(jwtHeader); final String algorithmName = jwtHeader.has("alg") ? jwtHeader.get("alg").asText() : null; if (jwtHeader.get("alg") == null) { @@ -229,11 +230,10 @@ protected Algorithm getAlgorithm(final JsonNode jwtHeader) throws JWTAlgorithmEx return Algorithm.findByName(algorithmName); } - protected JsonNode decodeAndParse(final String b64String) throws IOException { + JsonNode decodeAndParse(final String b64String) throws IOException { Validate.notNull(b64String); final String jsonString = new String(decoder.decode(b64String), "UTF-8"); - final JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); - return jwtHeader; + return mapper.readValue(jsonString, JsonNode.class); } } diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 8529929c..4afaf35c 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -24,53 +24,62 @@ public class JWTVerifierTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - @Test(expected = IllegalArgumentException.class) + @Test public void constructorShouldFailOnEmptySecret() { + expectedException.expect(IllegalArgumentException.class); new JWTVerifier(""); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailOn1Segments() throws Exception { - new JWTVerifier("such secret").verify("crypto"); + expectedException.expect(IllegalStateException.class); + signatureVerifier().verify("crypto"); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailOn2Segments() throws Exception { - new JWTVerifier("such secret").verify("much.crypto"); + expectedException.expect(IllegalStateException.class); + signatureVerifier().verify("much.crypto"); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailOn4Segments() throws Exception { - new JWTVerifier("such secret").verify("much.crypto.so.token"); + expectedException.expect(IllegalStateException.class); + signatureVerifier().verify("much.crypto.so.token"); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailOnEmptyStringToken() throws Exception { - new JWTVerifier("such secret").verify(""); + expectedException.expect(IllegalStateException.class); + signatureVerifier().verify(""); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailOnNullToken() throws Exception { - new JWTVerifier("such secret").verify(null); + expectedException.expect(IllegalStateException.class); + signatureVerifier().verify(null); } - @Test(expected = IllegalStateException.class) + @Test public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { - new JWTVerifier("such secret").getAlgorithm(JsonNodeFactory.instance.objectNode()); + expectedException.expect(IllegalStateException.class); + signatureVerifier().getAlgorithm(JsonNodeFactory.instance.objectNode()); } - @Test(expected = JWTAlgorithmException.class) + @Test public void shouldFailIfAlgorithmIsNotSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "doge-crypt")); + expectedException.expect(JWTAlgorithmException.class); + signatureVerifier().getAlgorithm(createSingletonJSONNode("alg", "doge-crypt")); } @Test public void shouldWorkIfAlgorithmIsSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "HS256")); + signatureVerifier().getAlgorithm(createSingletonJSONNode("alg", "HS256")); } - @Test(expected = SignatureException.class) + @Test public void shouldFailOnInvalidSignature() throws Exception { + expectedException.expect(SignatureException.class); final String jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + "." + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + @@ -78,7 +87,7 @@ public void shouldFailOnInvalidSignature() throws Exception { "." + "suchsignature_plzvalidate_zomgtokens"; String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; - new JWTVerifier(secret, "audience").verifySignature(jws.split("\\."), Algorithm.HS256); + signatureVerifier(secret).verifySignature(jws.split("\\."), Algorithm.HS256); } @Test @@ -90,76 +99,80 @@ public void shouldVerifySignature() throws Exception { "." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; byte[] secret = decoder.decode("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); - new JWTVerifier(secret, "audience") + signatureVerifier(secret) .verifySignature(jws.split("\\."), Algorithm.HS256); } - @Test(expected = JWTExpiredException.class) + @Test public void shouldFailWhenExpired1SecondAgo() throws Exception { - new JWTVerifier("such secret").verifyExpiration( + expectedException.expect(JWTExpiredException.class); + signatureVerifier().verifyExpiration( createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); } @Test public void shouldVerifyExpiration() throws Exception { - new JWTVerifier("such secret").verifyExpiration( + signatureVerifier().verifyExpiration( createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); } @Test public void shouldVerifyIssuer() throws Exception { - new JWTVerifier("such secret", "amaze audience", "very issuer") + issuerVerifier("very issuer") .verifyIssuer(createSingletonJSONNode("iss", "very issuer")); } - @Test(expected = JWTIssuerException.class) + @Test public void shouldFailIssuer() throws Exception { - new JWTVerifier("such secret", "amaze audience", "very issuer") + expectedException.expect(JWTIssuerException.class); + issuerVerifier("very issuer") .verifyIssuer(createSingletonJSONNode("iss", "wow")); } @Test public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { expectedException.expect(JWTIssuerException.class); - new JWTVerifier("such secret", "amaze audience", "very issuer") + issuerVerifier("very issuer") .verifyIssuer(JsonNodeFactory.instance.objectNode()); } @Test public void shouldVerifyAudience() throws Exception { - new JWTVerifier("such secret", "amaze audience") + audienceVerifier("amaze audience") .verifyAudience(createSingletonJSONNode("aud", "amaze audience")); } - @Test(expected = JWTAudienceException.class) + @Test public void shouldFailAudience() throws Exception { - new JWTVerifier("such secret", "amaze audience") + expectedException.expect(JWTAudienceException.class); + audienceVerifier("amaze audience") .verifyAudience(createSingletonJSONNode("aud", "wow")); } @Test public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { expectedException.expect(JWTAudienceException.class); - new JWTVerifier("such secret", "amaze audience") + audienceVerifier("amaze audience") .verifyAudience(JsonNodeFactory.instance.objectNode()); } @Test public void shouldVerifyNullAudience() throws Exception { - new JWTVerifier("such secret") + signatureVerifier() .verifyAudience(createSingletonJSONNode("aud", "wow")); } @Test public void shouldVerifyArrayAudience() throws Exception { - new JWTVerifier("such secret", "amaze audience") + audienceVerifier("amaze audience") .verifyAudience(createSingletonJSONNode("aud", new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); } - @Test(expected = JWTAudienceException.class) + @Test public void shouldFailArrayAudience() throws Exception { - new JWTVerifier("such secret", "amaze audience") + expectedException.expect(JWTAudienceException.class); + audienceVerifier("amaze audience") .verifyAudience(createSingletonJSONNode("aud", new ObjectMapper().readValue("[ \"foo\" ]", ArrayNode.class))); } @@ -191,13 +204,33 @@ public void shouldVerifyIssuerFromToken() throws Exception { verifier.verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.wLlz9xDltxqKHQC7BeauPi5Q4KQK4nDjlRqQPvKVLYk"); } - public static JsonNode createSingletonJSONNode(String key, String value) { + private static JWTVerifier signatureVerifier() { + return new JWTVerifier("such secret"); + } + + private static JWTVerifier signatureVerifier(String secret) { + return new JWTVerifier(secret); + } + + private static JWTVerifier signatureVerifier(byte[] secret) { + return new JWTVerifier(secret); + } + + private static JWTVerifier issuerVerifier(String issuer) { + return new JWTVerifier("such secret", null, issuer); + } + + private static JWTVerifier audienceVerifier(String audience) { + return new JWTVerifier("such secret", audience); + } + + private static JsonNode createSingletonJSONNode(String key, String value) { final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); jsonNodes.put(key, value); return jsonNodes; } - public static JsonNode createSingletonJSONNode(String key, JsonNode value) { + private static JsonNode createSingletonJSONNode(String key, JsonNode value) { final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); jsonNodes.put(key, value); return jsonNodes; From 7841789cbbc0eeb5c05198380081c52f92b5b187 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Wed, 13 Jul 2016 10:43:02 -0300 Subject: [PATCH 73/92] Add coverage reports --- .travis.yml | 2 ++ pom.xml | 26 ++++++++++++++++--- .../com/auth0/jwt/JWTRoundTripRsa256Test.java | 5 +--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index e20d23ce..ef0da2f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,5 @@ jdk: branches: only: - master +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/pom.xml b/pom.xml index aa06a2d8..4eef1601 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,8 @@ http://www.jwt.io - 1.7 + 1.7 + 1.7 com.auth0.jwt.internal @@ -86,11 +87,30 @@ maven-compiler-plugin 3.1 - ${java.version} - ${java.version} + ${java.source.version} + ${java.target.version} UTF-8 + + org.jacoco + jacoco-maven-plugin + 0.7.7.201606060606 + + + + prepare-agent + + + + report + test + + report + + + + org.apache.maven.plugins maven-shade-plugin diff --git a/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java b/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java index 2ea1af6e..722de2a4 100644 --- a/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java +++ b/src/test/java/com/auth0/jwt/JWTRoundTripRsa256Test.java @@ -10,10 +10,7 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static com.auth0.jwt.pem.PemReader.readPrivateKey; import static com.auth0.jwt.pem.PemReader.readPublicKey; From 3facb66e7a559ad243e0f2154127a53c9fb0023b Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Wed, 13 Jul 2016 15:09:55 -0300 Subject: [PATCH 74/92] Add badges --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0afc9d20..9c482b12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Java JWT +[![Build Status](https://travis-ci.org/auth0/java-jwt.svg?branch=master)](https://travis-ci.org/auth0/java-jwt) +[![Coverage Status](https://img.shields.io/codecov/c/github/auth0/java-jwt/master.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) +[![Maven Central](https://img.shields.io/maven-central/v/com.auth0/java-jwt.svg)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22java-jwt%22) + An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. ## Installation From 25abc1314b16fd7da76d3ef1d1eb29c6d23dfed1 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Wed, 13 Jul 2016 15:13:26 -0300 Subject: [PATCH 75/92] [maven-release-plugin] prepare release java-jwt-2.2.0 --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 4eef1601..63d3227c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 @@ -10,7 +9,7 @@ com.auth0 java-jwt - 2.2.1-SNAPSHOT + 2.2.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 7cd84ff69b5a87fa849be65084cbc322ac12ff04 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Wed, 13 Jul 2016 15:13:33 -0300 Subject: [PATCH 76/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 63d3227c..de21e688 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.0 + 2.2.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From d72f98fd060204a1687f0d896d88930505c016fc Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Fri, 22 Jul 2016 10:00:50 -0300 Subject: [PATCH 77/92] Update README.md [skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c482b12..e01536a0 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o com.auth0 java-jwt - 2.2.1 + 2.2.0 ``` ### Gradle ```gradle -compile 'com.auth0.java-jwt:2.2.1' +compile 'com.auth0.java-jwt:2.2.0' ``` ## Usage From 6a5099eb369c0e5d52f226b4e9e5fc827d5b314b Mon Sep 17 00:00:00 2001 From: Richard Seldon Date: Tue, 9 Aug 2016 19:28:33 +0900 Subject: [PATCH 78/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e01536a0..8d8ab4d7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o ### Gradle ```gradle -compile 'com.auth0.java-jwt:2.2.0' +compile 'com.auth0:java-jwt:2.2.0' ``` ## Usage From 217af05d16d62e462f5fa4456c090c1af9e1a51e Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Tue, 23 Aug 2016 22:36:33 -0300 Subject: [PATCH 79/92] Improve tests --- .../com/auth0/jwt/JWTVerifierRsa256Test.java | 31 ++++++++++++++++--- .../java/com/auth0/jwt/JWTVerifierTest.java | 9 ++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java b/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java index a4c1b86e..485954ce 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierRsa256Test.java @@ -1,9 +1,16 @@ package com.auth0.jwt; +import com.auth0.jwt.pem.X509CertUtils; +import org.apache.commons.codec.binary.Base64; import org.junit.Test; +import java.io.File; +import java.nio.file.Files; import java.security.PublicKey; import java.security.SignatureException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; import static com.auth0.jwt.pem.PemReader.readPublicKey; import static junit.framework.TestCase.assertNotNull; @@ -13,10 +20,10 @@ */ public class JWTVerifierRsa256Test { - public final static String RESOURCES_DIR = "src/test/resources/auth0-pem/"; - public final static String MISMATCHED_RESOURCES_DIR = "src/test/resources/test-pem/"; - public final static String PUBLIC_KEY_PEM_FILENAME = "key.pem"; - public final static String MISMATCHED_PUBLIC_KEY_PEM_FILENAME = "test-auth0.pem"; + private final static String RESOURCES_DIR = "src/test/resources/auth0-pem/"; + private final static String MISMATCHED_RESOURCES_DIR = "src/test/resources/test-pem/"; + private final static String PUBLIC_KEY_PEM_FILENAME = "key.pem"; + private final static String MISMATCHED_PUBLIC_KEY_PEM_FILENAME = "test-auth0.pem"; @@ -67,7 +74,7 @@ public void shouldFailOnInvalidJWTTokenSignature() throws Exception { /** * Here we modify the payload section on an otherwise legal JWT Token and check verification using the correct Public Key and - * unaltered JWT signnature (which now doesn't match the payload) fails + * unaltered JWT signature (which now doesn't match the payload) fails */ @Test(expected = SignatureException.class) public void shouldFailOnInvalidJWTTokenPayload() throws Exception { @@ -81,5 +88,19 @@ public void shouldFailOnInvalidJWTTokenPayload() throws Exception { new JWTVerifier(publicKey, "audience").verifySignature(token.split("\\."), Algorithm.RS256); } + @Test(expected = IllegalStateException.class) + public void shouldFailWithJwtThaHasTamperedAlgorithm() throws Exception { + final File file = new File(RESOURCES_DIR + PUBLIC_KEY_PEM_FILENAME); + final byte[] data = Files.readAllBytes(file.toPath()); + JWTSigner signer = new JWTSigner(data); + Map claims = new HashMap<>(); + claims.put("sub", "userid"); + JWTSigner.Options options = new JWTSigner.Options(); + options.setAlgorithm(Algorithm.HS256); + String jwt = signer.sign(claims, options); + new JWTVerifier(data).verify(jwt); + final PublicKey publicKey = readPublicKey(RESOURCES_DIR + PUBLIC_KEY_PEM_FILENAME); + new JWTVerifier(publicKey).verify(jwt); + } } diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 4afaf35c..e11fd5ae 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -103,6 +103,15 @@ public void shouldVerifySignature() throws Exception { .verifySignature(jws.split("\\."), Algorithm.HS256); } + @Test + public void shouldFailWithJwtThaHasTamperedAlgorithm() throws Exception { + expectedException.expect(IllegalStateException.class); + String tamperedAlg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.QRsN2SlYJ3EEn7P9dnZGsq9tjyv3giOWzZJzhy67zZs"; + byte[] secret = decoder.decode("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); + JWTVerifier verifier = new JWTVerifier(secret); + verifier.verify(tamperedAlg); + } + @Test public void shouldFailWhenExpired1SecondAgo() throws Exception { expectedException.expect(JWTExpiredException.class); From 559da8412bc7ea84ca5d4e1692e9c0669573fbf1 Mon Sep 17 00:00:00 2001 From: Andrea Ratto Date: Fri, 7 Oct 2016 02:40:11 +0200 Subject: [PATCH 80/92] Update commons-codec & commons-io (#71) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index de21e688..82b7913c 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ commons-codec commons-codec - 1.4 + 1.10 @@ -63,7 +63,7 @@ - org.apache.commons + commons-io commons-io 1.3.2 From b5a03a531821693b8bd6ab607bcd21b0b4ba39b4 Mon Sep 17 00:00:00 2001 From: Marek Pazik Date: Fri, 7 Oct 2016 02:41:28 +0200 Subject: [PATCH 81/92] Made examples compiling and more consistent. (#70) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d8ab4d7..58d4da9d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ compile 'com.auth0:java-jwt:2.2.0' ```java final String issuer = "https://mydomain.com/"; -final String secret = "{{a secret used for signing}}"; +final String secret = "{{secret used for signing}}"; -final long iat = System.currentTimeMillis() / 1000l; // issued at claim +final long iat = System.currentTimeMillis() / 1000L; // issued at claim final long exp = iat + 60L; // expires claim. In this case the token expires in 60 seconds final JWTSigner signer = new JWTSigner(secret); @@ -51,7 +51,7 @@ final String jwt = signer.sign(claims); final String secret = "{{secret used for signing}}"; try { final JWTVerifier verifier = new JWTVerifier(secret); - final Map claims= jwtVerifier.verify(jwt); + final Map claims= verifier.verify(jwt); } catch (JWTVerifyException e) { // Invalid Token } @@ -63,7 +63,7 @@ try { final String secret = "{{secret used for signing}}"; try { final JWTVerifier verifier = new JWTVerifier(secret, "{{my-audience}}", "{{my-issuer}}"); - final Map claims= jwtVerifier.verify(jwt); + final Map claims= verifier.verify(jwt); } catch (JWTVerifyException e) { // Invalid Token } From 1bebd54df761d209e9a4893a09736b6006116206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez=20Alarc=C3=B3n?= Date: Thu, 6 Oct 2016 21:45:04 -0300 Subject: [PATCH 82/92] Update README.md (#62) Fix path Gradle. From 0ca7585d5b0d6fa3494f843ad0dee2d229090840 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 6 Oct 2016 21:52:39 -0300 Subject: [PATCH 83/92] Preparing release --- README.md | 4 ++-- codecov.yml | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 codecov.yml diff --git a/README.md b/README.md index 58d4da9d..b255d54e 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o com.auth0 java-jwt - 2.2.0 + 2.2.1 ``` ### Gradle ```gradle -compile 'com.auth0:java-jwt:2.2.0' +compile 'com.auth0:java-jwt:2.2.1' ``` ## Usage diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..63e5785f --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + precision: 2 + round: down + range: "70...100" + status: + patch: + default: + if_no_uploads: error + changes: true + project: + default: + target: auto + if_no_uploads: error +comment: false \ No newline at end of file From 187bb3217bb8e289ef2263c813ac45efeaf329fd Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 6 Oct 2016 21:53:30 -0300 Subject: [PATCH 84/92] [maven-release-plugin] prepare release java-jwt-2.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 82b7913c..decc662e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.1-SNAPSHOT + 2.2.1 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 25509897b8d34bc6611b814416abfb7980095f8e Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 6 Oct 2016 21:53:36 -0300 Subject: [PATCH 85/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index decc662e..f1abc18e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.1 + 2.2.2-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 4208cc4c0f65df989ca372f008e78b0400bd3a97 Mon Sep 17 00:00:00 2001 From: Jake W Date: Fri, 7 Oct 2016 14:35:46 +0800 Subject: [PATCH 86/92] Update README.md Corrected auth0 link issue. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b255d54e..93236bbd 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,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 75ed27a6584d455d65eda10719eb79d579ac3ff2 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 5 Dec 2016 15:10:29 +0000 Subject: [PATCH 87/92] Pass caught Exception directly to RuntimeException (#83) The Exception that has been caught should be wrapped by RuntimeException. By calling 'getCause', you're looking further up the chain for a cause, and the resulting value could be `null`. --- src/main/java/com/auth0/jwt/JWTSigner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 89f2dc07..828128ad 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -77,7 +77,7 @@ public String sign(final Map claims, final Options options) { segments.add(encodedSignature(join(segments, "."), algorithm)); return join(segments, "."); } catch (Exception e) { - throw new RuntimeException(e.getCause()); + throw new RuntimeException(e); } } From 2e5bb5c231332f467291ff5e94474f961806a650 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Mon, 5 Dec 2016 12:26:34 -0300 Subject: [PATCH 88/92] [maven-release-plugin] prepare release java-jwt-2.2.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f1abc18e..3d2e4686 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.2-SNAPSHOT + 2.2.2 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 23022bdc768c0f266b2285b4c7baf1b73a021d94 Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Mon, 5 Dec 2016 12:26:40 -0300 Subject: [PATCH 89/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3d2e4686..bf7660d6 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.2 + 2.2.3-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From d63423320944e98da144563745b5eeb93c8a1c03 Mon Sep 17 00:00:00 2001 From: Michael Brizic Date: Thu, 27 Apr 2017 16:10:56 +0000 Subject: [PATCH 90/92] remove commons-io maven compile dependency, not referenced --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index bf7660d6..f0b97262 100644 --- a/pom.xml +++ b/pom.xml @@ -62,12 +62,6 @@ 3.4 - - commons-io - commons-io - 1.3.2 - - From fde1016c456209f611dae5a6470f8b3be067bb5a Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 4 May 2017 17:24:39 -0300 Subject: [PATCH 91/92] [maven-release-plugin] prepare release java-jwt-2.3.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f0b97262..615e1bae 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.2.3-SNAPSHOT + 2.3.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From ac54ceff0088dd1f6e9b7d2c889e920405ad1daf Mon Sep 17 00:00:00 2001 From: Hernan Zalazar Date: Thu, 4 May 2017 17:24:47 -0300 Subject: [PATCH 92/92] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 615e1bae..b1c69ea2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.3.0 + 2.3.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08.