Skip to content

Commit 3e05c56

Browse files
committed
add HS verification
1 parent 535af31 commit 3e05c56

12 files changed

Lines changed: 306 additions & 44 deletions

File tree

lib/src/main/java/com/auth0/jwtdecodejava/JWTDecoder.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.jwtdecodejava;
22

3+
import com.auth0.jwtdecodejava.enums.Algorithm;
34
import com.auth0.jwtdecodejava.exceptions.JWTException;
45
import com.auth0.jwtdecodejava.impl.JWTParser;
56
import com.auth0.jwtdecodejava.interfaces.Claim;
@@ -27,23 +28,15 @@ public static JWT decode(String jwt) {
2728
}
2829

2930
private void parseToken(String token) throws JWTException {
30-
final String[] parts = splitToken(token);
31+
final String[] parts = Utils.splitToken(token);
3132
final JWTParser converter = new JWTParser();
3233
header = converter.parseHeader(base64Decode(parts[0]));
3334
payload = converter.parsePayload(base64Decode(parts[1]));
3435
signature = parts[2];
3536
}
3637

37-
private String[] splitToken(String token) {
38-
String[] parts = token.split("\\.");
39-
if (parts.length != 3) {
40-
throw new JWTException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
41-
}
42-
return parts;
43-
}
44-
4538
@Override
46-
public String getAlgorithm() {
39+
public Algorithm getAlgorithm() {
4740
return header.getAlgorithm();
4841
}
4942

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package com.auth0.jwtdecodejava;
22

3+
import com.auth0.jwtdecodejava.enums.Algorithm;
34
import com.auth0.jwtdecodejava.exceptions.JWTException;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.sun.istack.internal.Nullable;
66
import org.apache.commons.codec.binary.Base64;
77
import org.apache.commons.codec.binary.StringUtils;
88

9-
import java.io.IOException;
9+
import javax.crypto.Mac;
10+
import javax.crypto.spec.SecretKeySpec;
11+
import java.security.InvalidKeyException;
12+
import java.security.MessageDigest;
13+
import java.security.NoSuchAlgorithmException;
1014

11-
class Utils {
15+
public class Utils {
1216

1317
@Nullable
14-
static String base64Decode(String string) throws JWTException {
18+
public static String base64Decode(String string) throws JWTException {
1519
String decoded;
1620
try {
1721
decoded = StringUtils.newStringUtf8(Base64.decodeBase64(string));
@@ -22,7 +26,7 @@ static String base64Decode(String string) throws JWTException {
2226
}
2327

2428
@Nullable
25-
static String base64Encode(String string) throws JWTException {
29+
public static String base64Encode(String string) throws JWTException {
2630
String encoded;
2731
try {
2832
encoded = StringUtils.newStringUtf8(Base64.encodeBase64(string.getBytes(), false, true));
@@ -31,4 +35,26 @@ static String base64Encode(String string) throws JWTException {
3135
}
3236
return encoded;
3337
}
38+
39+
public static boolean verifyHS(String[] jwtParts, String secret, Algorithm algorithm) throws NoSuchAlgorithmException, InvalidKeyException {
40+
if (secret == null) {
41+
throw new IllegalArgumentException("The Secret cannot be null");
42+
}
43+
if (algorithm != Algorithm.HS256 && algorithm != Algorithm.HS384 && algorithm != Algorithm.HS512) {
44+
throw new IllegalArgumentException("The Algorithm must be one of HS256, HS384, or HS512.");
45+
}
46+
Mac mac = Mac.getInstance(algorithm.toString());
47+
mac.init(new SecretKeySpec(secret.getBytes(), algorithm.toString()));
48+
String message = String.format("%s.%s", jwtParts[0], jwtParts[1]);
49+
byte[] result = mac.doFinal(message.getBytes());
50+
return MessageDigest.isEqual(result, Base64.decodeBase64(jwtParts[2]));
51+
}
52+
53+
public static String[] splitToken(String token) {
54+
String[] parts = token.split("\\.");
55+
if (parts.length != 3) {
56+
throw new JWTException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
57+
}
58+
return parts;
59+
}
3460
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.auth0.jwtdecodejava.enums;
2+
3+
import org.apache.commons.codec.digest.HmacAlgorithms;
4+
5+
public enum Algorithm {
6+
none(null),
7+
HS256(HmacAlgorithms.HMAC_SHA_256.toString()),
8+
HS384(HmacAlgorithms.HMAC_SHA_384.toString()),
9+
HS512(HmacAlgorithms.HMAC_SHA_512.toString());
10+
11+
private final String description;
12+
13+
Algorithm(String description) {
14+
this.description = description;
15+
}
16+
17+
@Override
18+
public String toString() {
19+
return description;
20+
}
21+
22+
public static Algorithm parseFrom(String algorithmName) {
23+
try {
24+
return Algorithm.valueOf(algorithmName);
25+
} catch (IllegalArgumentException ignored) {
26+
return null;
27+
}
28+
}
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.auth0.jwtdecodejava.exceptions;
2+
3+
public class AlgorithmMismatchException extends JWTException{
4+
public AlgorithmMismatchException(String message) {
5+
super(message);
6+
}
7+
}

lib/src/main/java/com/auth0/jwtdecodejava/exceptions/InvalidClaimException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
public class InvalidClaimException extends JWTException {
5-
public InvalidClaimException(String description) {
6-
super(description);
5+
public InvalidClaimException(String message) {
6+
super(message);
77
}
88
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.auth0.jwtdecodejava.exceptions;
2+
3+
import com.auth0.jwtdecodejava.enums.Algorithm;
4+
5+
public class SignatureVerificationException extends JWTException {
6+
public SignatureVerificationException(Algorithm algorithm) {
7+
this(algorithm, null);
8+
}
9+
10+
public SignatureVerificationException(Algorithm algorithm, Throwable cause) {
11+
super("The Token's Signature resulted invalid when verified using the Algorithm: " + algorithm.name(), cause);
12+
}
13+
}

lib/src/main/java/com/auth0/jwtdecodejava/impl/HeaderImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.jwtdecodejava.impl;
22

3+
import com.auth0.jwtdecodejava.enums.Algorithm;
34
import com.auth0.jwtdecodejava.interfaces.Header;
45
import com.fasterxml.jackson.databind.JsonNode;
56

@@ -16,8 +17,9 @@ class HeaderImpl implements Header {
1617
}
1718

1819
@Override
19-
public String getAlgorithm() {
20-
return extractClaim(ALGORITHM, tree).asString();
20+
public Algorithm getAlgorithm() {
21+
String alg = extractClaim(ALGORITHM, tree).asString();
22+
return Algorithm.parseFrom(alg);
2123
}
2224

2325
@Override

lib/src/main/java/com/auth0/jwtdecodejava/impl/JWTVerifier.java

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
package com.auth0.jwtdecodejava.impl;
22

33
import com.auth0.jwtdecodejava.JWTDecoder;
4+
import com.auth0.jwtdecodejava.Utils;
5+
import com.auth0.jwtdecodejava.enums.Algorithm;
6+
import com.auth0.jwtdecodejava.exceptions.AlgorithmMismatchException;
47
import com.auth0.jwtdecodejava.exceptions.InvalidClaimException;
58
import com.auth0.jwtdecodejava.exceptions.JWTException;
9+
import com.auth0.jwtdecodejava.exceptions.SignatureVerificationException;
610
import com.auth0.jwtdecodejava.interfaces.JWT;
711

12+
import java.security.InvalidKeyException;
13+
import java.security.NoSuchAlgorithmException;
814
import java.util.Arrays;
915
import java.util.Date;
1016
import java.util.HashMap;
1117
import java.util.Map;
1218

1319
public class JWTVerifier {
20+
private final Algorithm algorithm;
21+
private final String secret;
1422
private final Map<String, Object> claims;
1523

16-
private JWTVerifier() {
24+
private JWTVerifier(Algorithm algorithm, String secret) {
25+
this.algorithm = algorithm;
26+
this.secret = secret;
1727
this.claims = new HashMap<>();
1828
}
1929

20-
public static JWTVerifier init(){
21-
return new JWTVerifier();
30+
public static JWTVerifier init(Algorithm algorithm, String secret) throws IllegalArgumentException {
31+
if (algorithm == null) {
32+
throw new IllegalArgumentException("The Algorithm cannot be null.");
33+
}
34+
switch (algorithm) {
35+
case HS256:
36+
case HS384:
37+
case HS512:
38+
if (secret == null) {
39+
throw new IllegalArgumentException(String.format("You can't use the %s algorithm without providing a valid Secret.", algorithm.name()));
40+
}
41+
break;
42+
default:
43+
}
44+
return new JWTVerifier(algorithm, secret);
2245
}
2346

2447
public JWTVerifier withIssuer(String issuer) {
@@ -56,10 +79,33 @@ public JWTVerifier withJWTId(String jwtId) {
5679
return this;
5780
}
5881

59-
public JWT verify(String jwt) throws JWTException{
60-
JWT decode = JWTDecoder.decode(jwt);
61-
verifyClaims(decode, claims);
62-
return decode;
82+
public JWT verify(String token) throws JWTException {
83+
JWT jwt = JWTDecoder.decode(token);
84+
verifyClaims(jwt, claims);
85+
verifyAlgorithm(jwt, algorithm);
86+
verifySignature(Utils.splitToken(token));
87+
return jwt;
88+
}
89+
90+
private void verifySignature(String[] parts) {
91+
switch (algorithm) {
92+
case HS256:
93+
case HS384:
94+
case HS512:
95+
try {
96+
Utils.verifyHS(parts, secret, algorithm);
97+
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
98+
throw new SignatureVerificationException(algorithm, e);
99+
}
100+
break;
101+
default:
102+
}
103+
}
104+
105+
private void verifyAlgorithm(JWT jwt, Algorithm expectedAlgorithm) throws AlgorithmMismatchException, IllegalArgumentException {
106+
if (!expectedAlgorithm.equals(jwt.getAlgorithm())) {
107+
throw new AlgorithmMismatchException("The provided Algorithm doesn't match the one defined in the JWT's Header.");
108+
}
63109
}
64110

65111
private void verifyClaims(JWT jwt, Map<String, Object> claims) {

lib/src/main/java/com/auth0/jwtdecodejava/interfaces/Header.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.auth0.jwtdecodejava.interfaces;
22

3+
import com.auth0.jwtdecodejava.enums.Algorithm;
34
import com.sun.istack.internal.Nullable;
45

56
import java.util.Map;
67

78
public interface Header {
89

910
@Nullable
10-
String getAlgorithm();
11+
Algorithm getAlgorithm();
1112

1213
@Nullable
1314
String getType();

lib/src/test/java/com/auth0/jwtdecodejava/JWTDecoderTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.jwtdecodejava;
22

3+
import com.auth0.jwtdecodejava.enums.Algorithm;
34
import com.auth0.jwtdecodejava.exceptions.JWTException;
45
import com.auth0.jwtdecodejava.impl.BaseClaim;
56
import com.auth0.jwtdecodejava.interfaces.Claim;
@@ -76,7 +77,7 @@ public void shouldGetStringToken() throws Exception {
7677
public void shouldGetHeader() throws Exception {
7778
JWT jwt = JWTDecoder.decode("eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ");
7879
assertThat(jwt, is(notNullValue()));
79-
assertThat(jwt.getAlgorithm(), is("HS256"));
80+
assertThat(jwt.getAlgorithm(), is(Algorithm.HS256));
8081
}
8182

8283
@Test

0 commit comments

Comments
 (0)