Skip to content

Commit 11e96ad

Browse files
committed
update KeyProvider interface. Update readme.
1 parent c76d073 commit 11e96ad

File tree

11 files changed

+112
-103
lines changed

11 files changed

+112
-103
lines changed

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,38 +64,46 @@ Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
6464

6565
#### Using a KeyProvider:
6666

67-
By using a `KeyProvider` the library delegates the decision of which key to use in each case to the user. For the verification process, this means that the provider will be asked for a `PublicKey` with a given **Key Id** value. Your provider implementation should have the logic to fetch the right key, for example by parsing a JWKS file from a public domain like [auth0/jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) does. For the signing process, this means that the provider will be asked for a `PrivateKey` and it's associated **Key Id**, so it can set it in the Token's header for future verification in the same way. Check the [IETF draft](https://tools.ietf.org/html/rfc7517) for more information on how to implement this.
67+
By using a `KeyProvider` you can change in runtime the key used either to verify the token signature or to sign a new token for RSA or ECDSA algorithms. This is achieved by implementing either `RSAKeyProvider` or `ECDSAKeyProvider` methods:
68+
69+
- `getPublicKeyById(String kid)`: Its called during token signature verification and it should return the key used to verify the token. If key rotation is being used, e.g. [JWK](https://tools.ietf.org/html/rfc7517) it can fetch the correct rotation key using the id. (Or just return the same key all the time).
70+
- `getPrivateKey()`: Its called during token signing and it should return the key that will be used to sign the JWT.
71+
- `getPrivateKeyId()`: Its called during token signing and it should return the id of the key that identifies the one returned by `getPrivateKey()`. This value is preferred over the one set in the `JWTCreator.Builder#withKeyId(String)` method. If you don't need to set a `kid` value avoid instantiating an Algorithm using a `KeyProvider`.
72+
6873

6974
The following snippet uses example classes showing how this would work:
7075

7176

7277
```java
73-
final MyOwnJwkProvider jwkProvider = new MyOwnJwkProvider("{JWKS_FILE_HOST}");
74-
final RSAPrivateKey signingKey = //Get the key instance
75-
final String signingKeyId = //Create an Id for the above key
78+
final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");
79+
final RSAPrivateKey privateKey = //Get the key instance
80+
final String privateKeyId = //Create an Id for the above key
7681

7782
RSAKeyProvider keyProvider = new RSAKeyProvider() {
7883
@Override
79-
public RSAPublicKey getPublicKey(String keyId) {
80-
//Value might be null if it wasn't defined in the Token's header
81-
Jwk jwk = jwkProvider.get(keyId);
82-
return (RSAPublicKey) jwk.getPublicKey();
84+
public RSAPublicKey getPublicKeyById(String kid) {
85+
//Received 'kid' value might be null if it wasn't defined in the Token's header
86+
RSAPublicKey publicKey = jwkStore.get(kid);
87+
return (RSAPublicKey) publicKey;
8388
}
8489

8590
@Override
8691
public RSAPrivateKey getPrivateKey() {
87-
return signingKey;
92+
return privateKey;
8893
}
8994

9095
@Override
91-
public String getSigningKeyId() {
92-
return signingKeyId;
96+
public String getPrivateKeyId() {
97+
return privateKeyId;
9398
}
9499
};
100+
95101
Algorithm algorithm = Algorithm.RSA256(keyProvider);
96102
//Use the Algorithm to create and verify JWTs.
97103
```
98104

105+
> For simple key rotation using JWKs try the [jwks-rsa-java](https://github.com/auth0/jwks-rsa-java) library.
106+
99107

100108
### Create and Sign a Token
101109

@@ -271,7 +279,7 @@ When creating a Token with the `JWT.create()` you can specify header Claims by c
271279

272280
```java
273281
Map<String, Object> headerClaims = new HashMap();
274-
headerclaims.put("owner", "auth0");
282+
headerClaims.put("owner", "auth0");
275283
String token = JWT.create()
276284
.withHeader(headerClaims)
277285
.sign(algorithm);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public Builder withHeader(Map<String, Object> headerClaims) {
7777

7878
/**
7979
* Add a specific Key Id ("kid") claim to the Header.
80+
* If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider, the 'kid' value will be taken from that provider and this one will be ignored.
8081
*
8182
* @param keyId the Key Id value.
8283
* @return this same Builder instance.
@@ -304,7 +305,7 @@ public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCrea
304305
headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
305306
headerClaims.put(PublicClaims.TYPE, "JWT");
306307
String signingKeyId = algorithm.getSigningKeyId();
307-
if (!headerClaims.containsKey(PublicClaims.KEY_ID) && signingKeyId != null) {
308+
if (signingKeyId != null) {
308309
withKeyId(signingKeyId);
309310
}
310311
return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
6-
import com.auth0.jwt.interfaces.ECKeyProvider;
6+
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
77
import com.auth0.jwt.interfaces.RSAKeyProvider;
88

99
import java.io.UnsupportedEncodingException;
@@ -208,7 +208,7 @@ public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException {
208208
* @return a valid ECDSA256 Algorithm.
209209
* @throws IllegalArgumentException if the Key Provider is null.
210210
*/
211-
public static Algorithm ECDSA256(ECKeyProvider keyProvider) throws IllegalArgumentException {
211+
public static Algorithm ECDSA256(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
212212
return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider);
213213
}
214214

@@ -230,7 +230,7 @@ public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey)
230230
* @param key the key to use in the verify or signing instance.
231231
* @return a valid ECDSA256 Algorithm.
232232
* @throws IllegalArgumentException if the provided Key is null.
233-
* @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECKeyProvider)}
233+
* @deprecated use {@link #ECDSA256(ECPublicKey, ECPrivateKey)} or {@link #ECDSA256(ECDSAKeyProvider)}
234234
*/
235235
@Deprecated
236236
public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException {
@@ -246,7 +246,7 @@ public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException {
246246
* @return a valid ECDSA384 Algorithm.
247247
* @throws IllegalArgumentException if the Key Provider is null.
248248
*/
249-
public static Algorithm ECDSA384(ECKeyProvider keyProvider) throws IllegalArgumentException {
249+
public static Algorithm ECDSA384(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
250250
return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, keyProvider);
251251
}
252252

@@ -268,7 +268,7 @@ public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey)
268268
* @param key the key to use in the verify or signing instance.
269269
* @return a valid ECDSA384 Algorithm.
270270
* @throws IllegalArgumentException if the provided Key is null.
271-
* @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} or {@link #ECDSA384(ECKeyProvider)}
271+
* @deprecated use {@link #ECDSA384(ECPublicKey, ECPrivateKey)} or {@link #ECDSA384(ECDSAKeyProvider)}
272272
*/
273273
@Deprecated
274274
public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException {
@@ -284,7 +284,7 @@ public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException {
284284
* @return a valid ECDSA512 Algorithm.
285285
* @throws IllegalArgumentException if the Key Provider is null.
286286
*/
287-
public static Algorithm ECDSA512(ECKeyProvider keyProvider) throws IllegalArgumentException {
287+
public static Algorithm ECDSA512(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
288288
return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider);
289289
}
290290

@@ -306,7 +306,7 @@ public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey)
306306
* @param key the key to use in the verify or signing instance.
307307
* @return a valid ECDSA512 Algorithm.
308308
* @throws IllegalArgumentException if the provided Key is null.
309-
* @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} or {@link #ECDSA512(ECKeyProvider)}
309+
* @deprecated use {@link #ECDSA512(ECPublicKey, ECPrivateKey)} or {@link #ECDSA512(ECDSAKeyProvider)}
310310
*/
311311
@Deprecated
312312
public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException {

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
6-
import com.auth0.jwt.interfaces.ECKeyProvider;
6+
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
77
import org.apache.commons.codec.binary.Base64;
88

99
import java.nio.charset.StandardCharsets;
@@ -15,12 +15,12 @@
1515

1616
class ECDSAAlgorithm extends Algorithm {
1717

18-
private final ECKeyProvider keyProvider;
18+
private final ECDSAKeyProvider keyProvider;
1919
private final CryptoHelper crypto;
2020
private final int ecNumberSize;
2121

2222
//Visible for testing
23-
ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException {
23+
ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
2424
super(id, algorithm);
2525
if (keyProvider == null) {
2626
throw new IllegalArgumentException("The Key Provider cannot be null.");
@@ -30,7 +30,7 @@ class ECDSAAlgorithm extends Algorithm {
3030
this.ecNumberSize = ecNumberSize;
3131
}
3232

33-
ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECKeyProvider keyProvider) throws IllegalArgumentException {
33+
ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
3434
this(new CryptoHelper(), id, algorithm, ecNumberSize, keyProvider);
3535
}
3636

@@ -40,7 +40,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
4040
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
4141

4242
try {
43-
ECPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId());
43+
ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
4444
if (publicKey == null) {
4545
throw new IllegalStateException("The given Public Key is null.");
4646
}
@@ -72,7 +72,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
7272

7373
@Override
7474
public String getSigningKeyId() {
75-
return keyProvider.getSigningKeyId();
75+
return keyProvider.getPrivateKeyId();
7676
}
7777

7878
private boolean isDERSignature(byte[] signature) {
@@ -138,13 +138,13 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) {
138138
}
139139

140140
//Visible for testing
141-
static ECKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) {
141+
static ECDSAKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPrivateKey privateKey) {
142142
if (publicKey == null && privateKey == null) {
143143
throw new IllegalArgumentException("Both provided Keys cannot be null.");
144144
}
145-
return new ECKeyProvider() {
145+
return new ECDSAKeyProvider() {
146146
@Override
147-
public ECPublicKey getPublicKey(String keyId) {
147+
public ECPublicKey getPublicKeyById(String keyId) {
148148
return publicKey;
149149
}
150150

@@ -154,7 +154,7 @@ public ECPrivateKey getPrivateKey() {
154154
}
155155

156156
@Override
157-
public String getSigningKeyId() {
157+
public String getPrivateKeyId() {
158158
return null;
159159
}
160160
};

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
3838
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
3939

4040
try {
41-
RSAPublicKey publicKey = keyProvider.getPublicKey(jwt.getKeyId());
41+
RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
4242
if (publicKey == null) {
4343
throw new IllegalStateException("The given Public Key is null.");
4444
}
@@ -66,7 +66,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
6666

6767
@Override
6868
public String getSigningKeyId() {
69-
return keyProvider.getSigningKeyId();
69+
return keyProvider.getPrivateKeyId();
7070
}
7171

7272
//Visible for testing
@@ -76,7 +76,7 @@ static RSAKeyProvider providerForKeys(final RSAPublicKey publicKey, final RSAPri
7676
}
7777
return new RSAKeyProvider() {
7878
@Override
79-
public RSAPublicKey getPublicKey(String keyId) {
79+
public RSAPublicKey getPublicKeyById(String keyId) {
8080
return publicKey;
8181
}
8282

@@ -86,7 +86,7 @@ public RSAPrivateKey getPrivateKey() {
8686
}
8787

8888
@Override
89-
public String getSigningKeyId() {
89+
public String getPrivateKeyId() {
9090
return null;
9191
}
9292
};

lib/src/main/java/com/auth0/jwt/interfaces/ECKeyProvider.java renamed to lib/src/main/java/com/auth0/jwt/interfaces/ECDSAKeyProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
/**
77
* Elliptic Curve (EC) Public/Private Key provider.
88
*/
9-
public interface ECKeyProvider extends KeyProvider<ECPublicKey, ECPrivateKey> {
9+
public interface ECDSAKeyProvider extends KeyProvider<ECPublicKey, ECPrivateKey> {
1010
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@
1212
interface KeyProvider<U extends PublicKey, R extends PrivateKey> {
1313

1414
/**
15-
* Getter for the Public Key instance, used to verify the signature.
15+
* Getter for the Public Key instance with the given Id. Used to verify the signature on the JWT verification stage.
1616
*
1717
* @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.
1818
* @return the Public Key instance
1919
*/
20-
U getPublicKey(String keyId);
20+
U getPublicKeyById(String keyId);
2121

2222
/**
23-
* Getter for the Private Key instance, used to sign the content.
23+
* Getter for the Private Key instance. Used to sign the content on the JWT signing stage.
2424
*
2525
* @return the Private Key instance
2626
*/
2727
R getPrivateKey();
2828

2929
/**
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.
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.
3131
*
32-
* @return the Key Id that identifies the Signing Key or null if it's not specified.
32+
* @return the Key Id that identifies the Private Key or null if it's not specified.
3333
*/
34-
String getSigningKeyId();
34+
String getPrivateKeyId();
3535
}

lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.auth0.jwt;
22

33
import com.auth0.jwt.algorithms.Algorithm;
4-
import com.auth0.jwt.interfaces.ECKeyProvider;
4+
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
55
import com.auth0.jwt.interfaces.RSAKeyProvider;
66
import org.apache.commons.codec.binary.Base64;
77
import org.junit.Rule;
@@ -66,10 +66,10 @@ public void shouldAddKeyId() throws Exception {
6666
}
6767

6868
@Test
69-
public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingRSAAlgorithms() throws Exception {
69+
public void shouldAddKeyIdIfAvailableFromRSAAlgorithms() throws Exception {
7070
RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA");
7171
RSAKeyProvider provider = mock(RSAKeyProvider.class);
72-
when(provider.getSigningKeyId()).thenReturn("my-key-id");
72+
when(provider.getPrivateKeyId()).thenReturn("my-key-id");
7373
when(provider.getPrivateKey()).thenReturn(privateKey);
7474

7575
String signed = JWTCreator.init()
@@ -82,10 +82,10 @@ public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingRSAAlgorithms() thro
8282
}
8383

8484
@Test
85-
public void shouldNotOverwriteKeyIdIfAlreadySetUsingRSAAlgorithms() throws Exception {
85+
public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception {
8686
RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_RSA, "RSA");
8787
RSAKeyProvider provider = mock(RSAKeyProvider.class);
88-
when(provider.getSigningKeyId()).thenReturn("my-key-id");
88+
when(provider.getPrivateKeyId()).thenReturn("my-key-id");
8989
when(provider.getPrivateKey()).thenReturn(privateKey);
9090

9191
String signed = JWTCreator.init()
@@ -95,14 +95,14 @@ public void shouldNotOverwriteKeyIdIfAlreadySetUsingRSAAlgorithms() throws Excep
9595
assertThat(signed, is(notNullValue()));
9696
String[] parts = signed.split("\\.");
9797
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
98-
assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id"));
98+
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
9999
}
100100

101101
@Test
102-
public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingECDSAAlgorithms() throws Exception {
102+
public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception {
103103
ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC");
104-
ECKeyProvider provider = mock(ECKeyProvider.class);
105-
when(provider.getSigningKeyId()).thenReturn("my-key-id");
104+
ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class);
105+
when(provider.getPrivateKeyId()).thenReturn("my-key-id");
106106
when(provider.getPrivateKey()).thenReturn(privateKey);
107107

108108
String signed = JWTCreator.init()
@@ -115,10 +115,10 @@ public void shouldAddKeyIdIfAvailableAndNotAlreadyAddedUsingECDSAAlgorithms() th
115115
}
116116

117117
@Test
118-
public void shouldNotOverwriteKeyIdIfAlreadySetUsingECDSAAlgorithms() throws Exception {
118+
public void shouldNotOverwriteKeyIdIfAddedFromECDSAAlgorithms() throws Exception {
119119
ECPrivateKey privateKey = (ECPrivateKey) PemUtils.readPrivateKeyFromFile(PRIVATE_KEY_FILE_EC_256, "EC");
120-
ECKeyProvider provider = mock(ECKeyProvider.class);
121-
when(provider.getSigningKeyId()).thenReturn("my-key-id");
120+
ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class);
121+
when(provider.getPrivateKeyId()).thenReturn("my-key-id");
122122
when(provider.getPrivateKey()).thenReturn(privateKey);
123123

124124
String signed = JWTCreator.init()
@@ -128,7 +128,7 @@ public void shouldNotOverwriteKeyIdIfAlreadySetUsingECDSAAlgorithms() throws Exc
128128
assertThat(signed, is(notNullValue()));
129129
String[] parts = signed.split("\\.");
130130
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
131-
assertThat(headerJson, JsonMatcher.hasEntry("kid", "real-key-id"));
131+
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
132132
}
133133

134134
@Test

0 commit comments

Comments
 (0)