Skip to content

Commit 54d5897

Browse files
committed
add ECDSA Algorithm verify
1 parent 4ff0da2 commit 54d5897

18 files changed

Lines changed: 285 additions & 29 deletions

lib/src/main/java/com/auth0/jwtdecodejava/algorithms/Algorithm.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public abstract class Algorithm {
1313
private final String description;
1414

1515
/**
16-
* Creates a new Algorithms instance using SHA256withRSA. Tokens specify this as "RS256".
16+
* Creates a new Algorithm instance using SHA256withRSA. Tokens specify this as "RS256".
1717
*
1818
* @param publicKey the key to use in the verify instance.
1919
* @return a valid RSA256 Algorithm.
@@ -23,7 +23,7 @@ public static Algorithm RSA256(PublicKey publicKey) {
2323
}
2424

2525
/**
26-
* Creates a new Algorithms instance using SHA384withRSA. Tokens specify this as "RS384".
26+
* Creates a new Algorithm instance using SHA384withRSA. Tokens specify this as "RS384".
2727
*
2828
* @param publicKey the key to use in the verify instance.
2929
* @return a valid RSA384 Algorithm.
@@ -33,7 +33,7 @@ public static Algorithm RSA384(PublicKey publicKey) {
3333
}
3434

3535
/**
36-
* Creates a new Algorithms instance using SHA512withRSA. Tokens specify this as "RS512".
36+
* Creates a new Algorithm instance using SHA512withRSA. Tokens specify this as "RS512".
3737
*
3838
* @param publicKey the key to use in the verify instance.
3939
* @return a valid RSA512 Algorithm.
@@ -43,7 +43,7 @@ public static Algorithm RSA512(PublicKey publicKey) {
4343
}
4444

4545
/**
46-
* Creates a new Algorithms instance using HmacSHA256. Tokens specify this as "HS256".
46+
* Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256".
4747
*
4848
* @param secret the secret to use in the verify instance.
4949
* @return a valid HMAC256 Algorithm.
@@ -53,7 +53,7 @@ public static Algorithm HMAC256(String secret) {
5353
}
5454

5555
/**
56-
* Creates a new Algorithms instance using HmacSHA384. Tokens specify this as "HS384".
56+
* Creates a new Algorithm instance using HmacSHA384. Tokens specify this as "HS384".
5757
*
5858
* @param secret the secret to use in the verify instance.
5959
* @return a valid HMAC384 Algorithm.
@@ -63,7 +63,7 @@ public static Algorithm HMAC384(String secret) {
6363
}
6464

6565
/**
66-
* Creates a new Algorithms instance using HmacSHA512. Tokens specify this as "HS512".
66+
* Creates a new Algorithm instance using HmacSHA512. Tokens specify this as "HS512".
6767
*
6868
* @param secret the secret to use in the verify instance.
6969
* @return a valid HMAC512 Algorithm.
@@ -72,6 +72,36 @@ public static Algorithm HMAC512(String secret) {
7272
return new HMACAlgorithm("HS512", "HmacSHA512", secret);
7373
}
7474

75+
/**
76+
* Creates a new Algorithm instance using SHA256withECDSA. Tokens specify this as "ES256".
77+
*
78+
* @param publicKey the key to use in the verify instance.
79+
* @return a valid ECDSA256 Algorithm.
80+
*/
81+
public static Algorithm ECDSA256(PublicKey publicKey) {
82+
return new ECDSAAlgorithm("ES256", "SHA256withECDSA", publicKey);
83+
}
84+
85+
/**
86+
* Creates a new Algorithm instance using SHA384withECDSA. Tokens specify this as "ES384".
87+
*
88+
* @param publicKey the key to use in the verify instance.
89+
* @return a valid ECDSA384 Algorithm.
90+
*/
91+
public static Algorithm ECDSA384(PublicKey publicKey) {
92+
return new ECDSAAlgorithm("ES384", "SHA384withECDSA", publicKey);
93+
}
94+
95+
/**
96+
* Creates a new Algorithm instance using SHA512withECDSA. Tokens specify this as "ES512".
97+
*
98+
* @param publicKey the key to use in the verify instance.
99+
* @return a valid ECDSA512 Algorithm.
100+
*/
101+
public static Algorithm ECDSA512(PublicKey publicKey) {
102+
return new ECDSAAlgorithm("ES512", "SHA512withECDSA", publicKey);
103+
}
104+
75105
public static Algorithm none() {
76106
return new NoneAlgorithm();
77107
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.auth0.jwtdecodejava.algorithms;
2+
3+
import com.auth0.jwtdecodejava.exceptions.SignatureVerificationException;
4+
import org.apache.commons.codec.binary.Base64;
5+
6+
import java.security.*;
7+
8+
class ECDSAAlgorithm extends Algorithm {
9+
10+
private final PublicKey publicKey;
11+
12+
ECDSAAlgorithm(String id, String algorithm, PublicKey publicKey) {
13+
super(id, algorithm);
14+
if (publicKey == null) {
15+
throw new IllegalArgumentException("The PublicKey cannot be null");
16+
}
17+
this.publicKey = publicKey;
18+
}
19+
20+
PublicKey getPublicKey() {
21+
return publicKey;
22+
}
23+
24+
@Override
25+
public void verify(String[] jwtParts) throws SignatureVerificationException {
26+
try {
27+
String content = String.format("%s.%s", jwtParts[0], jwtParts[1]);
28+
Signature s = Signature.getInstance(getDescription());
29+
s.initVerify(publicKey);
30+
s.update(content.getBytes());
31+
byte[] signature = Base64.decodeBase64(jwtParts[2]);
32+
boolean valid = s.verify(signature);
33+
if (!valid) {
34+
throw new SignatureVerificationException(this);
35+
}
36+
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
37+
throw new SignatureVerificationException(this, e);
38+
}
39+
}
40+
41+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
import java.security.PublicKey;
99

10-
import static com.auth0.jwtdecodejava.PemUtils.readPublicKey;
10+
import static com.auth0.jwtdecodejava.PemUtils.readPublicKeyFromFile;
1111
import static org.hamcrest.Matchers.is;
1212
import static org.hamcrest.Matchers.notNullValue;
1313
import static org.junit.Assert.assertThat;
1414

1515
public class JWTTest {
1616

17-
private static final String PUBLIC_KEY_FILE = "src/test/resources/rsa_public.pem";
17+
private static final String PUBLIC_KEY_FILE = "src/test/resources/rsa-public.pem";
1818

1919
@Rule
2020
public ExpectedException exception = ExpectedException.none();
@@ -64,7 +64,7 @@ public void shouldAcceptHMAC512Algorithm() throws Exception {
6464
@Test
6565
public void shouldAcceptRSA256Algorithm() throws Exception {
6666
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.dxXF3MdsyW-AuvwJpaQtrZ33fAde9xWxpLIg9cO2tMLH2GSRNuLAe61KsJusZhqZB9Iy7DvflcmRz-9OZndm6cj_ThGeJH2LLc90K83UEvvRPo8l85RrQb8PcanxCgIs2RcZOLygERizB3pr5icGkzR7R2y6zgNCjKJ5_NJ6EiZsGN6_nc2PRK_DbyY-Wn0QDxIxKoA5YgQJ9qafe7IN980pXvQv2Z62c3XR8dYuaXBqhthBj-AbaFHEpZapN-V-TmuLNzR2MCB6Xr7BYMuCaqWf_XU8og4XNe8f_8w9Wv5vvgqMM1KhqVpG5VdMJv4o_L4NoCROHhtUQSLRh2M9cA";
67-
PublicKey key = readPublicKey(PUBLIC_KEY_FILE);
67+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA");
6868
JWT jwt = JWT.require(Algorithm.RSA256(key))
6969
.build()
7070
.verify(token);
@@ -76,7 +76,7 @@ public void shouldAcceptRSA256Algorithm() throws Exception {
7676
@Test
7777
public void shouldAcceptRSA384Algorithm() throws Exception {
7878
String token = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.TZlWjXObwGSQOiu2oMq8kiKz0_BR7bbBddNL6G8eZ_GoR82BXOZDqNrQr7lb_M-78XGBguWLWNIdYhzgxOUL9EoCJlrqVm9s9vo6G8T1sj1op-4TbjXZ61TwIvrJee9BvPLdKUJ9_fp1Js5kl6yXkst40Th8Auc5as4n49MLkipjpEhKDKaENKHpSubs1ripSz8SCQZSofeTM_EWVwSw7cpiM8Fy8jOPvWG8Xz4-e3ODFowvHVsDcONX_4FTMNbeRqDuHq2ZhCJnEfzcSJdrve_5VD5fM1LperBVslTrOxIgClOJ3RmM7-WnaizJrWP3D6Z9OLxPxLhM6-jx6tcxEw";
79-
PublicKey key = readPublicKey(PUBLIC_KEY_FILE);
79+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA");
8080
JWT jwt = JWT.require(Algorithm.RSA384(key))
8181
.build()
8282
.verify(token);
@@ -87,7 +87,7 @@ public void shouldAcceptRSA384Algorithm() throws Exception {
8787
@Test
8888
public void shouldAcceptRSA512Algorithm() throws Exception {
8989
String token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mvL5LoMyIrWYjk5umEXZTmbyIrkbbcVPUkvdGZbu0qFBxGOf0nXP5PZBvPcOu084lvpwVox5n3VaD4iqzW-PsJyvKFgi5TnwmsbKchAp7JexQEsQOnTSGcfRqeUUiBZqRQdYsho71oAB3T4FnalDdFEpM-fztcZY9XqKyayqZLreTeBjqJm4jfOWH7KfGBHgZExQhe96NLq1UA9eUyQwdOA1Z0SgXe4Ja5PxZ6Fm37KnVDtDlNnY4JAAGFo6y74aGNnp_BKgpaVJCGFu1f1S5xCQ1HSvs8ZSdVWs5NgawW3wRd0kRt_GJ_Y3mIwiF4qUyHWGtsSHu_qjVdCTtbFyow";
90-
PublicKey key = readPublicKey(PUBLIC_KEY_FILE);
90+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE, "RSA");
9191
JWT jwt = JWT.require(Algorithm.RSA512(key))
9292
.build()
9393
.verify(token);

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,18 @@
33
import org.bouncycastle.util.io.pem.PemObject;
44
import org.bouncycastle.util.io.pem.PemReader;
55

6-
import java.io.File;
7-
import java.io.FileNotFoundException;
8-
import java.io.FileReader;
9-
import java.io.IOException;
6+
import java.io.*;
107
import java.security.KeyFactory;
118
import java.security.NoSuchAlgorithmException;
129
import java.security.PublicKey;
10+
import java.security.interfaces.ECPublicKey;
1311
import java.security.spec.EncodedKeySpec;
1412
import java.security.spec.InvalidKeySpecException;
1513
import java.security.spec.X509EncodedKeySpec;
1614

1715
public class PemUtils {
1816

19-
static byte[] parsePEMFile(File pemFile) throws IOException {
17+
private static byte[] parsePEMFile(File pemFile) throws IOException {
2018
if (!pemFile.isFile() || !pemFile.exists()) {
2119
throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
2220
}
@@ -25,8 +23,7 @@ static byte[] parsePEMFile(File pemFile) throws IOException {
2523
return pemObject.getContent();
2624
}
2725

28-
29-
static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
26+
private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
3027
PublicKey publicKey = null;
3128
try {
3229
KeyFactory kf = KeyFactory.getInstance(algorithm);
@@ -41,9 +38,15 @@ static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
4138
return publicKey;
4239
}
4340

44-
public static PublicKey readPublicKey(String filepath) throws IOException {
41+
public static PublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {
4542
byte[] bytes = PemUtils.parsePEMFile(new File(filepath));
46-
return PemUtils.getPublicKey(bytes, "RSA");
43+
return PemUtils.getPublicKey(bytes, algorithm);
44+
}
45+
46+
public static ECPublicKey readPublicKeyFromRawString(String pemString, String algorithm) throws Exception {
47+
PemReader reader = new PemReader(new StringReader(pemString));
48+
PemObject pemObject = reader.readPemObject();
49+
return (ECPublicKey) PemUtils.getPublicKey(pemObject.getContent(), algorithm);
4750
}
4851

4952
}

lib/src/test/java/com/auth0/jwtdecodejava/algorithms/AlgorithmTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ public void shouldThrowRSA512VerificationWithNullPublicKey() throws Exception {
5858
Algorithm.RSA512(null);
5959
}
6060

61+
@Test
62+
public void shouldThrowECDSA256VerificationWithNullPublicKey() throws Exception {
63+
exception.expect(IllegalArgumentException.class);
64+
exception.expectMessage("The PublicKey cannot be null");
65+
Algorithm.ECDSA256(null);
66+
}
67+
68+
@Test
69+
public void shouldThrowECDSA384VerificationWithNullPublicKey() throws Exception {
70+
exception.expect(IllegalArgumentException.class);
71+
exception.expectMessage("The PublicKey cannot be null");
72+
Algorithm.ECDSA384(null);
73+
}
74+
75+
@Test
76+
public void shouldThrowECDSA512VerificationWithNullPublicKey() throws Exception {
77+
exception.expect(IllegalArgumentException.class);
78+
exception.expectMessage("The PublicKey cannot be null");
79+
Algorithm.ECDSA512(null);
80+
}
81+
6182
@Test
6283
public void shouldCreateHMAC256Algorithm() throws Exception {
6384
Algorithm algorithm = Algorithm.HMAC256("secret");
@@ -127,6 +148,42 @@ public void shouldCreateRSA512Algorithm() throws Exception {
127148
assertThat(((RSAAlgorithm) algorithm).getPublicKey(), is(key));
128149
}
129150

151+
@Test
152+
public void shouldCreateECDSA256Algorithm() throws Exception {
153+
PublicKey key = mock(PublicKey.class);
154+
Algorithm algorithm = Algorithm.ECDSA256(key);
155+
156+
assertThat(algorithm, is(notNullValue()));
157+
assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class)));
158+
assertThat(algorithm.getDescription(), is("SHA256withECDSA"));
159+
assertThat(algorithm.getName(), is("ES256"));
160+
assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key));
161+
}
162+
163+
@Test
164+
public void shouldCreateECDSA384Algorithm() throws Exception {
165+
PublicKey key = mock(PublicKey.class);
166+
Algorithm algorithm = Algorithm.ECDSA384(key);
167+
168+
assertThat(algorithm, is(notNullValue()));
169+
assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class)));
170+
assertThat(algorithm.getDescription(), is("SHA384withECDSA"));
171+
assertThat(algorithm.getName(), is("ES384"));
172+
assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key));
173+
}
174+
175+
@Test
176+
public void shouldCreateECDSA512Algorithm() throws Exception {
177+
PublicKey key = mock(PublicKey.class);
178+
Algorithm algorithm = Algorithm.ECDSA512(key);
179+
180+
assertThat(algorithm, is(notNullValue()));
181+
assertThat(algorithm, is(instanceOf(ECDSAAlgorithm.class)));
182+
assertThat(algorithm.getDescription(), is("SHA512withECDSA"));
183+
assertThat(algorithm.getName(), is("ES512"));
184+
assertThat(((ECDSAAlgorithm) algorithm).getPublicKey(), is(key));
185+
}
186+
130187
@Test
131188
public void shouldCreateNoneAlgorithm() throws Exception {
132189
Algorithm algorithm = Algorithm.none();
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.auth0.jwtdecodejava.algorithms;
2+
3+
import com.auth0.jwtdecodejava.exceptions.SignatureVerificationException;
4+
import org.junit.Rule;
5+
import org.junit.Test;
6+
import org.junit.rules.ExpectedException;
7+
8+
import java.security.PublicKey;
9+
10+
import static com.auth0.jwtdecodejava.PemUtils.readPublicKeyFromFile;
11+
12+
public class ECDSAAlgorithmTest {
13+
14+
private static final String PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public.pem";
15+
private static final String INVALID_PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public-invalid.pem";
16+
17+
private static final String PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public.pem";
18+
private static final String INVALID_PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public-invalid.pem";
19+
20+
private static final String PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public.pem";
21+
private static final String INVALID_PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public-invalid.pem";
22+
23+
@Rule
24+
public ExpectedException exception = ExpectedException.none();
25+
26+
@Test
27+
public void shouldPassECDSA256Verification() throws Exception {
28+
String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.W9qfN1b80B9hnMo49WL8THrOsf1vEjOhapeFemPMGySzxTcgfyudS5esgeBTO908X5SLdAr5jMwPUPBs9b6nNg";
29+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
30+
Algorithm algorithm = Algorithm.ECDSA256(key);
31+
algorithm.verify(jwt.split("\\."));
32+
}
33+
34+
@Test
35+
public void shouldFailECDSA256VerificationWithInvalidPublicKey() throws Exception {
36+
exception.expect(SignatureVerificationException.class);
37+
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA");
38+
String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.W9qfN1b80B9hnMo49WL8THrOsf1vEjOhapeFemPMGySzxTcgfyudS5esgeBTO908X5SLdAr5jMwPUPBs9b6nNg";
39+
Algorithm algorithm = Algorithm.ECDSA256(readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC"));
40+
algorithm.verify(jwt.split("\\."));
41+
}
42+
43+
@Test
44+
public void shouldPassECDSA384Verification() throws Exception {
45+
String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9._k5h1KyO-NE0R2_HAw0-XEc0bGT5atv29SxHhOGC9JDqUHeUdptfCK_ljQ01nLVt2OQWT2SwGs-TuyHDFmhPmPGFZ9wboxvq_ieopmYqhQilNAu-WF-frioiRz9733fU";
46+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC");
47+
Algorithm algorithm = Algorithm.ECDSA384(key);
48+
algorithm.verify(jwt.split("\\."));
49+
}
50+
51+
@Test
52+
public void shouldFailECDSA384VerificationWithInvalidPublicKey() throws Exception {
53+
exception.expect(SignatureVerificationException.class);
54+
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA");
55+
String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9._k5h1KyO-NE0R2_HAw0-XEc0bGT5atv29SxHhOGC9JDqUHeUdptfCK_ljQ01nLVt2OQWT2SwGs-TuyHDFmhPmPGFZ9wboxvq_ieopmYqhQilNAu-WF-frioiRz9733fU";
56+
Algorithm algorithm = Algorithm.ECDSA384(readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC"));
57+
algorithm.verify(jwt.split("\\."));
58+
}
59+
60+
@Test
61+
public void shouldPassECDSA512Verification() throws Exception {
62+
String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AZgdopFFsN0amCSs2kOucXdpylD31DEm5ChK1PG0_gq5Mf47MrvVph8zHSVuvcrXzcE1U3VxeCg89mYW1H33Y-8iAF0QFkdfTUQIWKNObH543WNMYYssv3OtOj0znPv8atDbaF8DMYAtcT1qdmaSJRhx-egRE9HGZkinPh9CfLLLt58X";
63+
PublicKey key = readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC");
64+
Algorithm algorithm = Algorithm.ECDSA512(key);
65+
algorithm.verify(jwt.split("\\."));
66+
}
67+
68+
@Test
69+
public void shouldFailECDSA512VerificationWithInvalidPublicKey() throws Exception {
70+
exception.expect(SignatureVerificationException.class);
71+
exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA");
72+
String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AZgdopFFsN0amCSs2kOucXdpylD31DEm5ChK1PG0_gq5Mf47MrvVph8zHSVuvcrXzcE1U3VxeCg89mYW1H33Y-8iAF0QFkdfTUQIWKNObH543WNMYYssv3OtOj0znPv8atDbaF8DMYAtcT1qdmaSJRhx-egRE9HGZkinPh9CfLLLt58X";
73+
Algorithm algorithm = Algorithm.ECDSA512(readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC"));
74+
algorithm.verify(jwt.split("\\."));
75+
}
76+
77+
}

0 commit comments

Comments
 (0)