Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and document thread-safety #427

Merged
merged 8 commits into from
Jun 16, 2020
8 changes: 8 additions & 0 deletions lib/src/main/java/com/auth0/jwt/ClockImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

import java.util.Date;

/**
* Default Clock implementation used for verification.
*
* @see Clock
* @see JWTVerifier
* <p>
* This class is thread-safe.
*/
final class ClockImpl implements Clock {

ClockImpl() {
Expand Down
4 changes: 3 additions & 1 deletion lib/src/main/java/com/auth0/jwt/JWTCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* This class is thread-safe.
*/
@SuppressWarnings("WeakerAccess")
public final class JWTCreator {
Expand Down Expand Up @@ -59,7 +61,7 @@ static JWTCreator.Builder init() {
*/
public static class Builder {
private final Map<String, Object> payloadClaims;
private Map<String, Object> headerClaims;
private final Map<String, Object> headerClaims;

Builder() {
this.payloadClaims = new HashMap<>();
Expand Down
2 changes: 2 additions & 0 deletions lib/src/main/java/com/auth0/jwt/JWTDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

/**
* The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation.
* <p>
* This class is thread-safe.
*/
@SuppressWarnings("WeakerAccess")
final class JWTDecoder implements DecodedJWT, Serializable {
Expand Down
6 changes: 4 additions & 2 deletions lib/src/main/java/com/auth0/jwt/JWTVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* This class is thread-safe.
*/
@SuppressWarnings("WeakerAccess")
public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
29 changes: 15 additions & 14 deletions lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* This class and its subclasses are thread-safe.
*/
@SuppressWarnings("WeakerAccess")
public abstract class Algorithm {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

white spaces are being added because the class wasn't properly formatted following the project defaults (IDE default settings).

* @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);
}

/**
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* This class is thread-safe.
*/
class CryptoHelper {

private static final byte JWT_PART_SEPARATOR = (byte)46;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;

/**
* Subclass representing an Elliptic Curve signing algorithm
* <p>
* This class is thread-safe.
*/
class ECDSAAlgorithm extends Algorithm {

private final ECDSAKeyProvider keyProvider;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <p>
* This class is thread-safe.
*/
class HMACAlgorithm extends Algorithm {

private final CryptoHelper crypto;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
* Subclass representing an RSA signing algorithm
* <p>
* This class is thread-safe.
*/
class RSAAlgorithm extends Algorithm {

private final RSAKeyProvider keyProvider;
Expand Down
11 changes: 9 additions & 2 deletions lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@
import java.io.IOException;
import java.util.Map;

/**
* Jackson deserializer implementation for converting from JWT Header parts.
*
* @see JWTParser
* <p>
* This class is thread-safe.
*/
class HeaderDeserializer extends StdDeserializer<BasicHeader> {

private final ObjectReader objectReader;

HeaderDeserializer(ObjectReader objectReader) {
this(null, objectReader);
}

private HeaderDeserializer(Class<?> vc, ObjectReader objectReader) {
super(vc);

this.objectReader = objectReader;
}

Expand Down
11 changes: 9 additions & 2 deletions lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@
import java.io.IOException;
import java.util.*;

/**
* Jackson deserializer implementation for converting from JWT Payload parts.
*
* @see JWTParser
* <p>
* This class is thread-safe.
*/
class PayloadDeserializer extends StdDeserializer<Payload> {

private final ObjectReader objectReader;

PayloadDeserializer(ObjectReader reader) {
this(null, reader);
}

private PayloadDeserializer(Class<?> vc, ObjectReader reader) {
super(vc);

this.objectReader = reader;
}

Expand Down
8 changes: 6 additions & 2 deletions lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <p>
* This class is thread-safe.
*/
class PayloadImpl implements Payload, Serializable {

Expand All @@ -30,7 +34,7 @@ class PayloadImpl implements Payload, Serializable {
PayloadImpl(String issuer, String subject, List<String> audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map<String, JsonNode> 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;
Expand Down
13 changes: 10 additions & 3 deletions lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <p>
* This class is thread-safe.
*/
public class PayloadSerializer extends StdSerializer<ClaimsHolder> {

public PayloadSerializer() {
Expand All @@ -20,14 +27,14 @@ private PayloadSerializer(Class<ClaimsHolder> t) {

@Override
public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider provider) throws IOException {

gen.writeStartObject();
for (Map.Entry<String, Object> 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();
Expand All @@ -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();
Expand Down
23 changes: 20 additions & 3 deletions lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -126,7 +126,7 @@ public void shouldValidateAudience() throws Exception {
.verify(tokenArr);

assertThat(jwtArr, is(notNullValue()));
}
}

@Test
public void shouldAcceptPartialAudience() throws Exception {
Expand Down Expand Up @@ -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 {
Expand Down
Loading