diff --git a/lib/src/main/java/com/auth0/jwt/ClockImpl.java b/lib/src/main/java/com/auth0/jwt/ClockImpl.java index 45e3edfc..29ed3c1e 100644 --- a/lib/src/main/java/com/auth0/jwt/ClockImpl.java +++ b/lib/src/main/java/com/auth0/jwt/ClockImpl.java @@ -4,6 +4,14 @@ import java.util.Date; +/** + * Default Clock implementation used for verification. + * + * @see Clock + * @see JWTVerifier + *

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

+ * This class is thread-safe. */ @SuppressWarnings("WeakerAccess") public final class JWTCreator { @@ -59,7 +61,7 @@ static JWTCreator.Builder init() { */ public static class Builder { private final Map payloadClaims; - private Map headerClaims; + private final Map headerClaims; Builder() { this.payloadClaims = new HashMap<>(); diff --git a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java index b14c2ac3..ffd0217c 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTDecoder.java +++ b/lib/src/main/java/com/auth0/jwt/JWTDecoder.java @@ -16,6 +16,8 @@ /** * The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation. + *

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

+ * This class is thread-safe. */ @SuppressWarnings("WeakerAccess") public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier { @@ -235,7 +237,7 @@ private static boolean isNullOrEmpty(String[] args) { return true; } boolean isAllNull = true; - for (String arg: args) { + for (String arg : args) { if (arg != null) { isAllNull = false; break; @@ -364,7 +366,7 @@ private void assertValidStringClaim(String claimName, String value, String expec } private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) { - Date today = clock.getToday(); + Date today = new Date(clock.getToday().getTime()); today.setTime(today.getTime() / 1000 * 1000); // truncate millis if (shouldBeFuture) { assertDateIsFuture(date, leeway, today); diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java index 24ad025b..1a8c6c2d 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java @@ -6,11 +6,12 @@ import com.auth0.jwt.interfaces.ECDSAKeyProvider; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.io.ByteArrayOutputStream; import java.security.interfaces.*; /** * The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token. + *

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

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

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

+ * This class is thread-safe. + */ class HMACAlgorithm extends Algorithm { private final CryptoHelper crypto; @@ -20,7 +26,7 @@ class HMACAlgorithm extends Algorithm { if (secretBytes == null) { throw new IllegalArgumentException("The Secret cannot be null"); } - this.secret = secretBytes; + this.secret = Arrays.copyOf(secretBytes, secretBytes.length); this.crypto = crypto; } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 15cce55a..a61fc97e 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -13,6 +13,11 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +/** + * Subclass representing an RSA signing algorithm + *

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

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

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

+ * This class is thread-safe. */ class PayloadImpl implements Payload, Serializable { @@ -30,7 +34,7 @@ class PayloadImpl implements Payload, Serializable { PayloadImpl(String issuer, String subject, List audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map tree, ObjectReader objectReader) { this.issuer = issuer; this.subject = subject; - this.audience = audience; + this.audience = audience != null ? Collections.unmodifiableList(audience) : null; this.expiresAt = expiresAt; this.notBefore = notBefore; this.issuedAt = issuedAt; diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java index dd0d2f42..684137cc 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java @@ -8,6 +8,13 @@ import java.util.Date; import java.util.Map; +/** + * Jackson serializer implementation for converting into JWT Payload parts. + * + * @see com.auth0.jwt.JWTCreator + *

+ * This class is thread-safe. + */ public class PayloadSerializer extends StdSerializer { public PayloadSerializer() { @@ -20,14 +27,14 @@ private PayloadSerializer(Class t) { @Override public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider provider) throws IOException { - + gen.writeStartObject(); for (Map.Entry e : holder.getClaims().entrySet()) { switch (e.getKey()) { case PublicClaims.AUDIENCE: if (e.getValue() instanceof String) { gen.writeFieldName(e.getKey()); - gen.writeString((String)e.getValue()); + gen.writeString((String) e.getValue()); break; } String[] audArray = (String[]) e.getValue(); @@ -37,7 +44,7 @@ public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider } else if (audArray.length > 1) { gen.writeFieldName(e.getKey()); gen.writeStartArray(); - for(String aud : audArray) { + for (String aud : audArray) { gen.writeString(aud); } gen.writeEndArray(); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index aec70c70..d57ca187 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -15,10 +15,10 @@ import java.util.HashMap; import java.util.Map; +import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class JWTVerifierTest { @@ -126,7 +126,7 @@ public void shouldValidateAudience() throws Exception { .verify(tokenArr); assertThat(jwtArr, is(notNullValue())); - } + } @Test public void shouldAcceptPartialAudience() throws Exception { @@ -505,6 +505,23 @@ public void shouldThrowOnNegativeCustomLeeway() throws Exception { .acceptLeeway(-1); } + @Test + public void shouldNotModifyOriginalClockDateWhenVerifying() throws Exception { + Clock clock = mock(Clock.class); + Date clockDate = spy(new Date(DATE_TOKEN_MS_VALUE)); + when(clock.getToday()).thenReturn(clockDate); + + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + JWTVerifier verifier = verification + .build(clock); + + DecodedJWT jwt = verifier.verify(token); + assertThat(jwt, is(notNullValue())); + + verify(clockDate, never()).setTime(anyLong()); + } + // Expires At @Test public void shouldValidateExpiresAtWithLeeway() throws Exception { diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index b70c8633..38aaf301 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -9,6 +9,7 @@ import org.junit.rules.ExpectedException; import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -39,6 +40,18 @@ public void shouldGetStringBytes() throws Exception { assertTrue(Arrays.equals(expectedBytes, HMACAlgorithm.getSecretBytes(text))); } + @Test + public void shouldCopyTheReceivedSecretArray() throws Exception { + String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; + byte[] secretArray = "secret".getBytes(Charset.defaultCharset()); + Algorithm algorithmString = Algorithm.HMAC256(secretArray); + + DecodedJWT decoded = JWT.decode(jwt); + algorithmString.verify(decoded); + secretArray[0] = secretArray[1]; + algorithmString.verify(decoded); + } + @Test public void shouldPassHMAC256Verification() throws Exception { String jwt = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M"; @@ -280,4 +293,4 @@ public void shouldBeEqualSignatureMethodResults() throws Exception { assertThat(algorithm.sign(bout.toByteArray()), is(algorithm.sign(header, payload))); } -} \ No newline at end of file +} diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index 56654427..3c604a30 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -13,10 +13,7 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,7 +36,7 @@ public class PayloadImplTest { public void setUp() throws Exception { mapper = getDefaultObjectMapper(); objectReader = mapper.reader(); - + expiresAt = Mockito.mock(Date.class); notBefore = Mockito.mock(Date.class); issuedAt = Mockito.mock(Date.class); @@ -56,6 +53,13 @@ public void shouldHaveUnmodifiableTree() throws Exception { payload.getTree().put("something", null); } + @Test + public void shouldHaveUnmodifiableAudience() throws Exception { + exception.expect(UnsupportedOperationException.class); + PayloadImpl payload = new PayloadImpl(null, null, new ArrayList(), null, null, null, null, null, objectReader); + payload.getAudience().add("something"); + } + @Test public void shouldGetIssuer() throws Exception { assertThat(payload, is(notNullValue()));