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

Performance improvements #255

Merged
merged 17 commits into from
Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion lib/src/main/java/com/auth0/jwt/JWT.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.impl.JWTParser;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;

@SuppressWarnings("WeakerAccess")
public abstract class JWT {
public class JWT {

private final JWTParser parser;

/**
* Constructs a new instance of the JWT library. Use this if you need to decode many JWT
* tokens on the fly and do not wish to instantiate a new parser for each invocation.
*/
public JWT() {
parser = new JWTParser();
}

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

/**
* Decode a given Json Web Token.
Expand Down
5 changes: 2 additions & 3 deletions lib/src/main/java/com/auth0/jwt/JWTCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,10 @@ private void addClaim(String name, Object value) {
private String sign() throws SignatureGenerationException {
String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
String content = String.format("%s.%s", header, payload);

byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
String signature = Base64.encodeBase64URLSafeString((signatureBytes));

return String.format("%s.%s", content, signature);
return String.format("%s.%s.%s", header, payload, signature);
}
}
5 changes: 4 additions & 1 deletion lib/src/main/java/com/auth0/jwt/JWTDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ final class JWTDecoder implements DecodedJWT {
private final Payload payload;

JWTDecoder(String jwt) throws JWTDecodeException {
this(new JWTParser(), jwt);
}

JWTDecoder(JWTParser converter, String jwt) throws JWTDecodeException {
parts = TokenUtils.splitToken(jwt);
final JWTParser converter = new JWTParser();
String headerJson;
String payloadJson;
try {
Expand Down
5 changes: 4 additions & 1 deletion lib/src/main/java/com/auth0/jwt/JWTVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.impl.JWTParser;
import com.auth0.jwt.impl.PublicClaims;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.Clock;
Expand All @@ -18,11 +19,13 @@ public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier {
private final Algorithm algorithm;
final Map<String, Object> claims;
private final Clock clock;
private final JWTParser parser;

JWTVerifier(Algorithm algorithm, Map<String, Object> claims, Clock clock) {
this.algorithm = algorithm;
this.claims = Collections.unmodifiableMap(claims);
this.clock = clock;
this.parser = new JWTParser();
}

/**
Expand Down Expand Up @@ -363,7 +366,7 @@ private void requireClaim(String name, Object value) {
*/
@Override
public DecodedJWT verify(String token) throws JWTVerificationException {
DecodedJWT jwt = JWT.decode(token);
DecodedJWT jwt = new JWTDecoder(parser, token);
return verify(jwt);
}

Expand Down
24 changes: 24 additions & 0 deletions lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;

import java.io.ByteArrayOutputStream;
import java.security.interfaces.*;

/**
Expand Down Expand Up @@ -361,12 +362,35 @@ public String toString() {
*/
public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException;

/**
* Sign the given content using this Algorithm instance.
*
* @param headerBytes an array of bytes representing the base64 encoded header content to be verified against the signature.
* @param payloadBytes an array of bytes representing the base64 encoded payload content to be verified against the signature.
* @return the signature in a base64 encoded array of bytes
* @throws SignatureGenerationException if the Key is invalid.
*/
public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException {
// default implementation; keep around until sign(byte[]) method is removed
byte[] contentBytes = new byte[headerBytes.length + 1 + payloadBytes.length];

System.arraycopy(headerBytes, 0, contentBytes, 0, headerBytes.length);
contentBytes[headerBytes.length] = (byte)'.';
System.arraycopy(payloadBytes, 0, contentBytes, headerBytes.length + 1, payloadBytes.length);

return sign(contentBytes);
}

/**
* Sign the given content using this Algorithm instance.
*
* @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature.
* @return the signature in a base64 encoded array of bytes
* @throws SignatureGenerationException if the Key is invalid.
* @deprecated Please use the {@linkplain #sign(byte[], byte[])} method instead.
*/

@Deprecated
public abstract byte[] sign(byte[] contentBytes) throws SignatureGenerationException;

}
175 changes: 175 additions & 0 deletions lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,202 @@

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.StandardCharsets;
import java.security.*;

class CryptoHelper {

private static final byte JWT_PART_SEPARATOR = (byte)46;

/**
* Verify signature for JWT header and payload.
*
* @param algorithm algorithm name.
* @param secretBytes algorithm secret.
* @param header JWT header.
* @param payload JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/

boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unless I'm missing something, this method could be safely deleted and you could use the byte[] header and payload variant instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See below comment

return verifySignatureFor(algorithm, secretBytes, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
}

/**
* Verify signature for JWT header and payload.
*
* @param algorithm algorithm name.
* @param secretBytes algorithm secret.
* @param header JWT header.
* @param payload JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/

boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, headerBytes, payloadBytes), signatureBytes);
}

/**
* Create signature for JWT header and payload.
*
* @param algorithm algorithm name.
* @param secretBytes algorithm secret.
* @param headerBytes JWT header.
* @param payloadBytes JWT payload.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/

byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException {
final Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secretBytes, algorithm));
mac.update(headerBytes);
mac.update(JWT_PART_SEPARATOR);
return mac.doFinal(payloadBytes);
}

/**
* Verify signature for JWT header and payload.
*
* @param algorithm algorithm name.
* @param publicKey algorithm public key.
* @param header JWT header.
* @param payload JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/

boolean verifySignatureFor(String algorithm, PublicKey publicKey, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as my comment above, unless this methods are called from many places passing String and as a convenience we want to wrap that here. What are your thoughts?

Copy link
Contributor Author

@skjolber skjolber Jan 25, 2019

Choose a reason for hiding this comment

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

Well, the header/payload from DecodedJWT are strings, so it is good to pass them directly so to the helper. Also there is quite a lot of unit test calls to this convenice method. I guess keeping them around is fair.

return verifySignatureFor(algorithm, publicKey, header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
}

/**
* Verify signature for JWT header and payload using a public key.
*
* @param algorithm algorithm name.
* @param publicKey the public key to use for verification.
* @param headerBytes JWT header.
* @param payloadBytes JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/

boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] headerBytes, byte[] payloadBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
s.initVerify(publicKey);
s.update(headerBytes);
s.update(JWT_PART_SEPARATOR);
s.update(payloadBytes);
return s.verify(signatureBytes);
}

/**
* Create signature for JWT header and payload using a private key.
*
* @param algorithm algorithm name.
* @param privateKey the private key to use for signing.
* @param headerBytes JWT header.
* @param payloadBytes JWT payload.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided.
*/

byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] headerBytes, byte[] payloadBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
s.initSign(privateKey);
s.update(headerBytes);
s.update(JWT_PART_SEPARATOR);
s.update(payloadBytes);
return s.sign();
}

/**
* Verify signature.
*
* @param algorithm algorithm name.
* @param secretBytes algorithm secret.
* @param contentBytes the content to which the signature applies.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @deprecated rather use corresponding method which takes header and payload as separate inputs
*/

@Deprecated
boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes);
}

/**
* Create signature.
*
* @param algorithm algorithm name.
* @param secretBytes algorithm secret.
* @param contentBytes the content to be signed.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @deprecated rather use corresponding method which takes header and payload as separate inputs
*/

@Deprecated
byte[] createSignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException {
final Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secretBytes, algorithm));
return mac.doFinal(contentBytes);
}

/**
* Verify signature using a public key.
*
* @param algorithm algorithm name.
* @param publicKey algorithm public key.
* @param contentBytes the content to which the signature applies.
* @param signatureBytes JWT signature.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided.
* @deprecated rather use corresponding method which takes header and payload as separate inputs
*/

@Deprecated
boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
s.initVerify(publicKey);
s.update(contentBytes);
return s.verify(signatureBytes);
}

/**
* Create signature using a private key.
*
* @param algorithm algorithm name.
* @param privateKey the private key to use for signing.
* @param contentBytes the content to be signed.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @throws SignatureException if this signature object is not initialized properly or if this signature algorithm is unable to process the input data provided.
* @deprecated rather use corresponding method which takes header and payload as separate inputs
*/

@Deprecated
byte[] createSignatureFor(String algorithm, PrivateKey privateKey, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
s.initSign(privateKey);
Expand Down
19 changes: 16 additions & 3 deletions lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
Expand Down Expand Up @@ -36,15 +35,14 @@ class ECDSAAlgorithm extends Algorithm {

@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());

try {
ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
if (publicKey == null) {
throw new IllegalStateException("The given Public Key is null.");
}
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, JOSEToDER(signatureBytes));
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes));

if (!valid) {
throw new SignatureVerificationException(this);
Expand All @@ -55,6 +53,21 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
}

@Override
public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException {
try {
ECPrivateKey privateKey = keyProvider.getPrivateKey();
if (privateKey == null) {
throw new IllegalStateException("The given Private Key is null.");
}
byte[] signature = crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes);
return DERToJOSE(signature);
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
throw new SignatureGenerationException(this, e);
}
}

@Override
@Deprecated
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
try {
ECPrivateKey privateKey = keyProvider.getPrivateKey();
Expand Down
Loading