Skip to content

Commit 3979a96

Browse files
author
Adam Englander
committed
Fixed bug in Jose4jJWTService#decode which caused null pointer exception when decoding a request JWT from a webhook
1 parent 2c17d25 commit 3979a96

File tree

5 files changed

+195
-14
lines changed

5 files changed

+195
-14
lines changed

CHANGELOG.md

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

33
This changelog references the relevant changes (bug and security fixes) for the lifetime of the library.
44

5+
* 4.2.1
6+
7+
* Fixed bug in Jose4jJWTService#decode which caused null pointer exception when decoding a request JWT from a webhook
8+
59
* 4.2.0
610

711
* Add Organization Service management

sdk/src/main/java/com/iovation/launchkey/sdk/crypto/jwt/JWTClaims.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class JWTClaims {
4141

4242
private final String contentHash;
4343

44-
private final int statusCode;
44+
private final Integer statusCode;
4545

4646
private final String cacheControlHeader;
4747

@@ -89,7 +89,7 @@ public JWTClaims(
8989
Integer expiresAt,
9090
String contentHashAlgorithm,
9191
String contentHash,
92-
int statusCode,
92+
Integer statusCode,
9393
String cacheControlHeader,
9494
String locationHeader
9595
) {
@@ -201,7 +201,7 @@ public String getContentHashAlgorithm() {
201201
* Get the response status code
202202
* @return The response status code
203203
*/
204-
public int getStatusCode() {
204+
public Integer getStatusCode() {
205205
return statusCode;
206206
}
207207

@@ -228,7 +228,7 @@ public boolean equals(Object o) {
228228

229229
JWTClaims jwtClaims = (JWTClaims) o;
230230

231-
if (statusCode != jwtClaims.statusCode) return false;
231+
if (statusCode != null ? !statusCode.equals(jwtClaims.statusCode) : jwtClaims.statusCode != null) return false;
232232
if (tokenId != null ? !tokenId.equals(jwtClaims.tokenId) : jwtClaims.tokenId != null) return false;
233233
if (issuer != null ? !issuer.equals(jwtClaims.issuer) : jwtClaims.issuer != null) return false;
234234
if (subject != null ? !subject.equals(jwtClaims.subject) : jwtClaims.subject != null) return false;
@@ -257,7 +257,7 @@ public int hashCode() {
257257
result = 31 * result + (expiresAt != null ? expiresAt.hashCode() : 0);
258258
result = 31 * result + (contentHashAlgorithm != null ? contentHashAlgorithm.hashCode() : 0);
259259
result = 31 * result + (contentHash != null ? contentHash.hashCode() : 0);
260-
result = 31 * result + statusCode;
260+
result = 31 * result + (statusCode != null ? statusCode.hashCode() : 0);
261261
result = 31 * result + (cacheControlHeader != null ? cacheControlHeader.hashCode() : 0);
262262
result = 31 * result + (locationHeader != null ? locationHeader.hashCode() : 0);
263263
return result;

sdk/src/main/java/com/iovation/launchkey/sdk/crypto/jwt/Jose4jJWTService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public JWTClaims decode(PublicKey publicKey, String expectedAudience, String exp
126126
JwtConsumer jwtConsumer = builder.build();
127127

128128
JwtClaims libraryClaims = jwtConsumer.processToClaims(jwt);
129-
Map responseClaims = libraryClaims.getClaimValue("response", Map.class);
129+
Map responseClaims = libraryClaims.getClaimValue("response", Map.class) == null ? new HashMap<String, Object>(): libraryClaims.getClaimValue("response", Map.class);
130130

131131
claims = new JWTClaims(
132132
libraryClaims.getJwtId(),
@@ -138,7 +138,7 @@ public JWTClaims decode(PublicKey publicKey, String expectedAudience, String exp
138138
(int) libraryClaims.getExpirationTime().getValue(),
139139
responseClaims.get("func") == null ? null : String.valueOf(responseClaims.get("func")),
140140
responseClaims.get("hash") == null ? null : String.valueOf(responseClaims.get("hash")),
141-
Integer.valueOf(String.valueOf(responseClaims.get("status"))),
141+
responseClaims.get("status") == null ? null :Integer.valueOf(String.valueOf(responseClaims.get("status"))),
142142
responseClaims.get("cache") == null ? null : String.valueOf(responseClaims.get("cache")),
143143
responseClaims.get("location") == null ? null : String.valueOf(responseClaims.get("location"))
144144
);

sdk/src/test/java/com/iovation/launchkey/sdk/crypto/jwt/JWTClaimsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public void getContentHashAlgorithm() throws Exception {
9191

9292
@Test
9393
public void getStatusCode() throws Exception {
94-
assertEquals(201, jwtClaims.getStatusCode());
94+
assertEquals(Integer.valueOf(201), jwtClaims.getStatusCode());
9595
}
9696

9797
@Test

sdk/src/test/java/com/iovation/launchkey/sdk/crypto/jwt/Jose4jJWTServiceTest.java

Lines changed: 183 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
import org.bouncycastle.jce.provider.BouncyCastleProvider;
66
import org.jose4j.jwa.AlgorithmConstraints;
77
import org.jose4j.jws.AlgorithmIdentifiers;
8+
import org.jose4j.jws.JsonWebSignature;
9+
import org.jose4j.jwt.JwtClaims;
810
import org.jose4j.jwt.consumer.JwtConsumer;
911
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
12+
import org.jose4j.lang.JoseException;
1013
import org.junit.Before;
1114
import org.junit.Test;
1215

@@ -46,8 +49,8 @@ public class Jose4jJWTServiceTest {
4649

4750
private Jose4jJWTService jwtService;
4851
private Date platformDate;
49-
private KeyPair keyPair;
50-
private String currentPrivateKeyId;
52+
private static KeyPair keyPair = null;
53+
private static String currentPrivateKeyId = null;
5154

5255
@SuppressWarnings("SpellCheckingInspection")
5356
private static final String PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\n" +
@@ -77,12 +80,14 @@ public class Jose4jJWTServiceTest {
7780

7881
@Before
7982
public void setUp() throws Exception {
80-
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", provider);
81-
keyPairGenerator.initialize(2048);
82-
keyPair = keyPairGenerator.generateKeyPair();
83+
if (keyPair == null) {
84+
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", provider);
85+
keyPairGenerator.initialize(2048);
86+
keyPair = keyPairGenerator.generateKeyPair();
87+
currentPrivateKeyId = JCECrypto.getRsaPublicKeyFingerprint(provider, (RSAPrivateKey) keyPair.getPrivate());
88+
}
8389
platformDate = new Date();
8490
privateKeys = new HashMap<>();
85-
currentPrivateKeyId = JCECrypto.getRsaPublicKeyFingerprint(provider, (RSAPrivateKey) keyPair.getPrivate());
8691
privateKeys.put(currentPrivateKeyId, (RSAPrivateKey) keyPair.getPrivate());
8792
jwtService = new Jose4jJWTService(
8893
PLATFORM_IDENTIFIER,
@@ -92,6 +97,7 @@ public void setUp() throws Exception {
9297
);
9398
}
9499

100+
95101
@Test
96102
public void encodeEncodesSomethingThatProperlyDecodes() throws Exception {
97103

@@ -197,4 +203,175 @@ public void testJwtDoesNotCollideForSameRequestDataHash() throws Exception {
197203
assertNotEquals(Hex.encodeHexString(sha256.digest(a.getBytes())),
198204
Hex.encodeHexString(sha256.digest(b.getBytes())));
199205
}
206+
207+
@Test
208+
public void testDecodeProcessesTID() throws Exception {
209+
JwtClaims jwtClaims = getBaseClaims();
210+
String expected = "Expected";
211+
jwtClaims.setJwtId(expected);
212+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
213+
final String actual =
214+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, expected, new Date(), jwt).getTokenId();
215+
assertEquals(expected, actual);
216+
}
217+
218+
@Test
219+
public void testDecodeProcessesIssuer() throws Exception {
220+
JwtClaims jwtClaims = getBaseClaims();
221+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
222+
final String actual =
223+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
224+
.getIssuer();
225+
assertEquals(PLATFORM_IDENTIFIER, actual);
226+
}
227+
228+
@Test
229+
public void testDecodeProcessesSubject() throws Exception {
230+
JwtClaims jwtClaims = getBaseClaims();
231+
String expected = "Expected";
232+
jwtClaims.setSubject(expected);
233+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
234+
final String actual =
235+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
236+
.getSubject();
237+
assertEquals(expected, actual);
238+
}
239+
240+
@Test
241+
public void testDecodeProcessesAudience() throws Exception {
242+
JwtClaims jwtClaims = getBaseClaims();
243+
String expected = "Expected";
244+
jwtClaims.setAudience(expected);
245+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
246+
final String actual =
247+
jwtService.decode(keyPair.getPublic(), expected, jwtClaims.getJwtId(), new Date(), jwt).getAudience();
248+
assertEquals(expected, actual);
249+
}
250+
251+
@Test
252+
public void testDecodeProcessesIssuedAt() throws Exception {
253+
JwtClaims jwtClaims = getBaseClaims();
254+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
255+
final long actual =
256+
(long) jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
257+
.getIssuedAt();
258+
assertEquals(jwtClaims.getIssuedAt().getValue(), actual);
259+
}
260+
261+
@Test
262+
public void testDecodeProcessesNotBefore() throws Exception {
263+
JwtClaims jwtClaims = getBaseClaims();
264+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
265+
final long actual =
266+
(long) jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
267+
.getNotBefore();
268+
assertEquals(jwtClaims.getNotBefore().getValue(), actual);
269+
}
270+
271+
@Test
272+
public void testDecodeProcessesExpiresAt() throws Exception {
273+
JwtClaims jwtClaims = getBaseClaims();
274+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
275+
final long actual =
276+
(long) jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
277+
.getExpiresAt();
278+
assertEquals(jwtClaims.getExpirationTime().getValue(), actual);
279+
}
280+
281+
@Test
282+
public void testDecodeProcessesResponseContentHash() throws Exception {
283+
JwtClaims jwtClaims = getBaseClaims();
284+
String expected = "Expected";
285+
Map<String, Object> response = new HashMap<>();
286+
response.put("hash", expected);
287+
jwtClaims.setClaim("response", response);
288+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
289+
final String actual =
290+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
291+
.getContentHash();
292+
assertEquals(expected, actual);
293+
}
294+
295+
@Test
296+
public void testDecodeProcessesResponseContentHashAlgorithm() throws Exception {
297+
JwtClaims jwtClaims = getBaseClaims();
298+
String expected = "Expected";
299+
Map<String, Object> response = new HashMap<>();
300+
response.put("func", expected);
301+
jwtClaims.setClaim("response", response);
302+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
303+
final String actual =
304+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
305+
.getContentHashAlgorithm();
306+
assertEquals(expected, actual);
307+
}
308+
309+
@Test
310+
public void testDecodeProcessesResponseStatusCode() throws Exception {
311+
JwtClaims jwtClaims = getBaseClaims();
312+
Integer expected = 200;
313+
Map<String, Object> response = new HashMap<>();
314+
response.put("status", expected);
315+
jwtClaims.setClaim("response", response);
316+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
317+
final Integer actual =
318+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
319+
.getStatusCode();
320+
assertEquals(expected, actual);
321+
}
322+
323+
@Test
324+
public void testDecodeProcessesResponseCacheControlHeader() throws Exception {
325+
JwtClaims jwtClaims = getBaseClaims();
326+
String expected = "Expected";
327+
Map<String, Object> response = new HashMap<>();
328+
response.put("cache", expected);
329+
jwtClaims.setClaim("response", response);
330+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
331+
final String actual =
332+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
333+
.getCacheControlHeader();
334+
assertEquals(expected, actual);
335+
}
336+
337+
@Test
338+
public void testDecodeProcessesResponseLocationHeader() throws Exception {
339+
JwtClaims jwtClaims = getBaseClaims();
340+
String expected = "Expected";
341+
Map<String, Object> response = new HashMap<>();
342+
response.put("location", expected);
343+
jwtClaims.setClaim("response", response);
344+
String jwt = getJwsCompactSerializationFromJtwClaims(jwtClaims);
345+
final String actual =
346+
jwtService.decode(keyPair.getPublic(), ENTITY_IDENTIFIER, jwtClaims.getJwtId(), new Date(), jwt)
347+
.getLocationHeader();
348+
assertEquals(expected, actual);
349+
}
350+
351+
private JwtClaims getBaseClaims() {
352+
JwtClaims jwtClaims = new JwtClaims();
353+
jwtClaims.setGeneratedJwtId();
354+
jwtClaims.setExpirationTimeMinutesInTheFuture(60000);
355+
jwtClaims.setAudience(ENTITY_IDENTIFIER);
356+
jwtClaims.setIssuer(PLATFORM_IDENTIFIER);
357+
jwtClaims.setIssuedAtToNow();
358+
jwtClaims.setNotBeforeMinutesInThePast(0);
359+
return jwtClaims;
360+
}
361+
362+
private String getJwsCompactSerializationFromJtwClaims(JwtClaims claims) throws JWTError {
363+
JsonWebSignature jws = new JsonWebSignature();
364+
jws.setKeyIdHeaderValue(currentPrivateKeyId);
365+
jws.setKey(privateKeys.get(currentPrivateKeyId));
366+
jws.setPayload(claims.toJson());
367+
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
368+
369+
String jwt;
370+
try {
371+
jwt = jws.getCompactSerialization();
372+
} catch (JoseException e) {
373+
throw new JWTError("An error occurred encoding the JWT", e);
374+
}
375+
return jwt;
376+
}
200377
}

0 commit comments

Comments
 (0)