Skip to content

Commit 0001be0

Browse files
committed
refactor KeyProvider to use KeyId
1 parent cf123a6 commit 0001be0

16 files changed

Lines changed: 831 additions & 247 deletions

lib/src/main/java/com/auth0/jwt/JWTCreator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea
303303
}
304304
headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
305305
headerClaims.put(PublicClaims.TYPE, "JWT");
306+
String signingKeyId = algorithm.getSigningKeyId();
307+
if (!headerClaims.containsKey(PublicClaims.KEY_ID) && signingKeyId != null) {
308+
withKeyId(signingKeyId);
309+
}
306310
return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
307311
}
308312

@@ -322,8 +326,8 @@ private void addClaim(String name, Object value) {
322326
}
323327

324328
private String sign() throws SignatureGenerationException {
325-
String header = Base64.encodeBase64URLSafeString((headerJson.getBytes(StandardCharsets.UTF_8)));
326-
String payload = Base64.encodeBase64URLSafeString((payloadJson.getBytes(StandardCharsets.UTF_8)));
329+
String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
330+
String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
327331
String content = String.format("%s.%s", header, payload);
328332

329333
byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));

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

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

33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
5+
import com.auth0.jwt.interfaces.DecodedJWT;
56
import com.auth0.jwt.interfaces.ECKeyProvider;
67
import com.auth0.jwt.interfaces.RSAKeyProvider;
78

@@ -324,6 +325,15 @@ protected Algorithm(String name, String description) {
324325
this.description = description;
325326
}
326327

328+
/**
329+
* Getter for the Id of the Private Key used to sign the tokens. This is usually specified as the `kid` claim in the Header.
330+
*
331+
* @return the Key Id that identifies the Signing Key or null if it's not specified.
332+
*/
333+
public String getSigningKeyId() {
334+
return null;
335+
}
336+
327337
/**
328338
* Getter for the name of this Algorithm, as defined in the JWT Standard. i.e. "HS256"
329339
*
@@ -348,13 +358,12 @@ public String toString() {
348358
}
349359

350360
/**
351-
* Verify the given content using this Algorithm instance.
361+
* Verify the given token using this Algorithm instance.
352362
*
353-
* @param contentBytes an array of bytes representing the base64 encoded content to be verified against the signature.
354-
* @param signatureBytes an array of bytes representing the base64 encoded signature to compare the content against.
363+
* @param jwt the already decoded JWT that it's going to be verified.
355364
* @throws SignatureVerificationException if the Token's Signature is invalid, meaning that it doesn't match the signatureBytes, or if the Key is invalid.
356365
*/
357-
public abstract void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException;
366+
public abstract void verify(DecodedJWT jwt) throws SignatureVerificationException;
358367

359368
/**
360369
* Sign the given content using this Algorithm instance.

lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
5+
import com.auth0.jwt.interfaces.DecodedJWT;
56
import com.auth0.jwt.interfaces.ECKeyProvider;
7+
import org.apache.commons.codec.binary.Base64;
68

9+
import java.nio.charset.StandardCharsets;
710
import java.security.InvalidKeyException;
811
import java.security.NoSuchAlgorithmException;
912
import java.security.SignatureException;
@@ -22,9 +25,6 @@ class ECDSAAlgorithm extends Algorithm {
2225
if (keyProvider == null) {
2326
throw new IllegalArgumentException("The Key Provider cannot be null.");
2427
}
25-
if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) {
26-
throw new IllegalArgumentException("Both provided Keys cannot be null.");
27-
}
2828
this.keyProvider = keyProvider;
2929
this.crypto = crypto;
3030
this.ecNumberSize = ecNumberSize;
@@ -34,24 +34,20 @@ class ECDSAAlgorithm extends Algorithm {
3434
this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider);
3535
}
3636

37-
ECPublicKey getPublicKey() {
38-
return keyProvider.getPublicKey();
39-
}
40-
41-
ECPrivateKey getPrivateKey() {
42-
return keyProvider.getPrivateKey();
43-
}
44-
4537
@Override
46-
public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException {
38+
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
39+
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
40+
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
41+
4742
try {
48-
if (keyProvider.getPublicKey() == null) {
43+
ECPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId());
44+
if (publicKey == null) {
4945
throw new IllegalStateException("The given Public Key is null.");
5046
}
5147
if (!isDERSignature(signatureBytes)) {
5248
signatureBytes = JOSEToDER(signatureBytes);
5349
}
54-
boolean valid = crypto.verifySignatureFor(getDescription(), keyProvider.getPublicKey(), contentBytes, signatureBytes);
50+
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes);
5551

5652
if (!valid) {
5753
throw new SignatureVerificationException(this);
@@ -64,15 +60,20 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV
6460
@Override
6561
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
6662
try {
67-
if (keyProvider.getPrivateKey() == null) {
63+
ECPrivateKey privateKey = keyProvider.getPrivateKey();
64+
if (privateKey == null) {
6865
throw new IllegalStateException("The given Private Key is null.");
6966
}
70-
return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes);
67+
return crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
7168
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
7269
throw new SignatureGenerationException(this, e);
7370
}
7471
}
7572

73+
@Override
74+
public String getSigningKeyId() {
75+
return keyProvider.getSigningKeyId();
76+
}
7677

7778
private boolean isDERSignature(byte[] signature) {
7879
// DER Structure: http://crypto.stackexchange.com/a/1797
@@ -138,16 +139,24 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) {
138139

139140
//Visible for testing
140141
static ECKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) {
142+
if (publicKey == null && privateKey == null) {
143+
throw new IllegalArgumentException("Both provided Keys cannot be null.");
144+
}
141145
return new ECKeyProvider() {
142146
@Override
143-
public ECPublicKey getPublicKey() {
147+
public ECPublicKey getPublicKey(String keyId) {
144148
return publicKey;
145149
}
146150

147151
@Override
148152
public ECPrivateKey getPrivateKey() {
149153
return privateKey;
150154
}
155+
156+
@Override
157+
public String getSigningKeyId() {
158+
return null;
159+
}
151160
};
152161
}
153162
}

lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java

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

33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
5+
import com.auth0.jwt.interfaces.DecodedJWT;
56
import org.apache.commons.codec.CharEncoding;
7+
import org.apache.commons.codec.binary.Base64;
68

79
import java.io.UnsupportedEncodingException;
10+
import java.nio.charset.StandardCharsets;
811
import java.security.InvalidKeyException;
912
import java.security.NoSuchAlgorithmException;
1013

@@ -13,6 +16,7 @@ class HMACAlgorithm extends Algorithm {
1316
private final CryptoHelper crypto;
1417
private final byte[] secret;
1518

19+
//Visible for testing
1620
HMACAlgorithm(CryptoHelper crypto, String id, String algorithm, byte[] secretBytes) throws IllegalArgumentException {
1721
super(id, algorithm);
1822
if (secretBytes == null) {
@@ -30,19 +34,19 @@ class HMACAlgorithm extends Algorithm {
3034
this(new CryptoHelper(), id, algorithm, getSecretBytes(secret));
3135
}
3236

37+
//Visible for testing
3338
static byte[] getSecretBytes(String secret) throws IllegalArgumentException, UnsupportedEncodingException {
3439
if (secret == null) {
3540
throw new IllegalArgumentException("The Secret cannot be null");
3641
}
3742
return secret.getBytes(CharEncoding.UTF_8);
3843
}
3944

40-
byte[] getSecret() {
41-
return secret;
42-
}
43-
4445
@Override
45-
public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException {
46+
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
47+
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
48+
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
49+
4650
try {
4751
boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes);
4852
if (!valid) {

lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
5+
import com.auth0.jwt.interfaces.DecodedJWT;
6+
import org.apache.commons.codec.binary.Base64;
57

68
class NoneAlgorithm extends Algorithm {
79

@@ -10,7 +12,8 @@ class NoneAlgorithm extends Algorithm {
1012
}
1113

1214
@Override
13-
public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException {
15+
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
16+
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
1417
if (signatureBytes.length > 0) {
1518
throw new SignatureVerificationException(this);
1619
}

lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
5+
import com.auth0.jwt.interfaces.DecodedJWT;
56
import com.auth0.jwt.interfaces.RSAKeyProvider;
7+
import org.apache.commons.codec.binary.Base64;
68

9+
import java.nio.charset.StandardCharsets;
710
import java.security.InvalidKeyException;
811
import java.security.NoSuchAlgorithmException;
912
import java.security.SignatureException;
@@ -21,9 +24,6 @@ class RSAAlgorithm extends Algorithm {
2124
if (keyProvider == null) {
2225
throw new IllegalArgumentException("The Key Provider cannot be null.");
2326
}
24-
if (keyProvider.getPublicKey() == null && keyProvider.getPrivateKey() == null) {
25-
throw new IllegalArgumentException("Both provided Keys cannot be null.");
26-
}
2727
this.keyProvider = keyProvider;
2828
this.crypto = crypto;
2929
}
@@ -32,21 +32,17 @@ class RSAAlgorithm extends Algorithm {
3232
this(new CryptoHelper(), id, algorithm, keyProvider);
3333
}
3434

35-
RSAPublicKey getPublicKey() {
36-
return keyProvider.getPublicKey();
37-
}
38-
39-
RSAPrivateKey getPrivateKey() {
40-
return keyProvider.getPrivateKey();
41-
}
42-
4335
@Override
44-
public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureVerificationException {
36+
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
37+
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
38+
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
39+
4540
try {
46-
if (keyProvider.getPublicKey() == null) {
41+
RSAPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId());
42+
if (publicKey == null) {
4743
throw new IllegalStateException("The given Public Key is null.");
4844
}
49-
boolean valid = crypto.verifySignatureFor(getDescription(), keyProvider.getPublicKey(), contentBytes, signatureBytes);
45+
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes);
5046
if (!valid) {
5147
throw new SignatureVerificationException(this);
5248
}
@@ -58,27 +54,41 @@ public void verify(byte[] contentBytes, byte[] signatureBytes) throws SignatureV
5854
@Override
5955
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
6056
try {
61-
if (keyProvider.getPrivateKey() == null) {
57+
RSAPrivateKey privateKey = keyProvider.getPrivateKey();
58+
if (privateKey == null) {
6259
throw new IllegalStateException("The given Private Key is null.");
6360
}
64-
return crypto.createSignatureFor(getDescription(), keyProvider.getPrivateKey(), contentBytes);
61+
return crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
6562
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
6663
throw new SignatureGenerationException(this, e);
6764
}
6865
}
6966

67+
@Override
68+
public String getSigningKeyId() {
69+
return keyProvider.getSigningKeyId();
70+
}
71+
7072
//Visible for testing
7173
static RSAKeyProvider providerForKeys(final RSAPublicKey publicKey, final RSAPrivateKey privateKey) {
74+
if (publicKey == null && privateKey == null) {
75+
throw new IllegalArgumentException("Both provided Keys cannot be null.");
76+
}
7277
return new RSAKeyProvider() {
7378
@Override
74-
public RSAPublicKey getPublicKey() {
79+
public RSAPublicKey getPublicKey(String keyId) {
7580
return publicKey;
7681
}
7782

7883
@Override
7984
public RSAPrivateKey getPrivateKey() {
8085
return privateKey;
8186
}
87+
88+
@Override
89+
public String getSigningKeyId() {
90+
return null;
91+
}
8292
};
8393
}
8494
}

lib/src/main/java/com/auth0/jwt/interfaces/KeyProvider.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ interface KeyProvider<U extends PublicKey, R extends PrivateKey> {
1414
/**
1515
* Getter for the Public Key instance, used to verify the signature.
1616
*
17+
* @param keyId the Key Id specified in the Token's Header or null if none is available. Provides a hint on which Public Key to use to verify the token's signature.
1718
* @return the Public Key instance
1819
*/
19-
U getPublicKey();
20+
U getPublicKey(String keyId);
2021

2122
/**
2223
* Getter for the Private Key instance, used to sign the content.
2324
*
2425
* @return the Private Key instance
2526
*/
2627
R getPrivateKey();
28+
29+
/**
30+
* Getter for the Id of the Private Key used to sign the tokens. This represents the `kid` claim and will be placed in the Header if no other "Key Id" has been set already.
31+
*
32+
* @return the Key Id that identifies the Signing Key or null if it's not specified.
33+
*/
34+
String getSigningKeyId();
2735
}

lib/src/main/java/com/auth0/jwt/interfaces/Signature.java

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)