Skip to content

Commit 74b4325

Browse files
authored
Merge pull request #225 from CyberSource/feature/jwt-utility
Added utility for parsing and verification of capture context response
2 parents 92de83c + 6f47aa8 commit 74b4325

3 files changed

Lines changed: 131 additions & 1 deletion

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@
304304
<dependency>
305305
<groupId>com.cybersource</groupId>
306306
<artifactId>AuthenticationSdk</artifactId>
307-
<version>0.0.39</version>
307+
<version>0.0.40-SNAPSHOT</version>
308308
</dependency>
309309

310310
<dependency>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package utilities.capturecontext.utility;
2+
3+
import com.cybersource.authsdk.cache.CacheForPublicKeys;
4+
import com.cybersource.authsdk.core.ConfigException;
5+
import com.cybersource.authsdk.core.MerchantConfig;
6+
import com.cybersource.authsdk.util.jwt.JWTUtility;
7+
import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwkException;
8+
import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwtException;
9+
import com.cybersource.authsdk.util.jwt.exceptions.JwtSignatureValidationException;
10+
import com.google.gson.Gson;
11+
import com.google.gson.JsonObject;
12+
import com.google.gson.JsonParser;
13+
import com.google.gson.reflect.TypeToken;
14+
import com.nimbusds.jose.JOSEException;
15+
import com.nimbusds.jose.JWSHeader;
16+
import com.nimbusds.jose.Payload;
17+
import com.nimbusds.jwt.SignedJWT;
18+
19+
import java.io.IOException;
20+
import java.lang.reflect.Type;
21+
import java.net.MalformedURLException;
22+
import java.security.interfaces.RSAPublicKey;
23+
import java.text.ParseException;
24+
import java.util.HashMap;
25+
26+
public class CaptureContextParsingUtility {
27+
private static CacheForPublicKeys cache = new CacheForPublicKeys();
28+
29+
public static JsonObject parseCaptureContextResponse(
30+
String jwtValue, MerchantConfig merchantConfig, boolean verifyJwt)
31+
throws InvalidJwtException, ConfigException, IOException, InvalidJwkException, JwtSignatureValidationException {
32+
SignedJWT signedJWT = JWTUtility.parse(jwtValue);
33+
34+
if (verifyJwt) {
35+
RSAPublicKey publicKey;
36+
boolean isJwtValid = false;
37+
38+
JWSHeader jwsHeader = signedJWT.getHeader();
39+
40+
String kid = jwsHeader.getKeyID();
41+
42+
boolean isPublicKeyFromCache = false;
43+
44+
try {
45+
publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
46+
isPublicKeyFromCache = true;
47+
} catch (NullPointerException e) {
48+
fetchPublicKeyFromApi(kid, merchantConfig.getRunEnvironment());
49+
publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
50+
}
51+
52+
try {
53+
assert publicKey != null;
54+
isJwtValid = JWTUtility.verifyJwt(signedJWT, publicKey);
55+
} catch (JwtSignatureValidationException e) {
56+
if (isPublicKeyFromCache) {
57+
fetchPublicKeyFromApi(kid, merchantConfig.getRunEnvironment());
58+
publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
59+
isJwtValid = JWTUtility.verifyJwt(signedJWT, publicKey);
60+
}
61+
}
62+
63+
if (!isJwtValid) {
64+
throw new JwtSignatureValidationException("JWT validation failed");
65+
}
66+
}
67+
68+
return convertPayloadMapToJsonObject(signedJWT.getPayload());
69+
}
70+
71+
private static JsonObject convertPayloadMapToJsonObject(Payload payload) {
72+
Gson gson = new Gson();
73+
Type typeObject = new TypeToken<HashMap<String, Object>>() {}.getType();
74+
String jsonRepresentation = gson.toJson(payload.toJSONObject(), typeObject);
75+
return JsonParser.parseString(jsonRepresentation).getAsJsonObject();
76+
}
77+
78+
private static void fetchPublicKeyFromApi(String kid, String runEnvironment) throws ConfigException, IOException, InvalidJwkException {
79+
RSAPublicKey publicKey;
80+
81+
try {
82+
publicKey = PublicKeyApiController.fetchPublicKey(kid, runEnvironment);
83+
cache.addPublicKeyToCache(runEnvironment, kid, publicKey);
84+
} catch (MalformedURLException err) {
85+
throw new ConfigException("Invalid Runtime URL in Merchant Config");
86+
} catch (IOException err) {
87+
throw new IOException("Error while trying to retrieve public key from server");
88+
} catch (ParseException err) {
89+
throw new InvalidJwkException("JWK received from server cannot be parsed correctly", err);
90+
} catch (JOSEException err) {
91+
throw new InvalidJwkException("Cannot convert JWK to RSA Public Key", err);
92+
}
93+
}
94+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package utilities.capturecontext.utility;
2+
3+
import com.cybersource.authsdk.util.jwt.JWTUtility;
4+
import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwkException;
5+
import com.nimbusds.jose.JOSEException;
6+
import okhttp3.Call;
7+
import okhttp3.OkHttpClient;
8+
import okhttp3.Request;
9+
import okhttp3.Response;
10+
11+
import java.io.IOException;
12+
import java.net.URL;
13+
import java.security.interfaces.RSAPublicKey;
14+
import java.text.ParseException;
15+
16+
public class PublicKeyApiController {
17+
public static RSAPublicKey fetchPublicKey(String kid, String runEnvironment)
18+
throws IOException, ParseException, JOSEException, InvalidJwkException {
19+
URL url = new URL("https://" + runEnvironment + "/flex/v2/public-keys/" + kid);
20+
21+
Request request = new Request.Builder().url(url).build();
22+
23+
OkHttpClient client = new OkHttpClient();
24+
25+
Call call = client.newCall(request);
26+
27+
String jwkJsonString;
28+
29+
try (Response response = call.execute()) {
30+
assert response.body() != null;
31+
jwkJsonString = response.body().string();
32+
}
33+
34+
return JWTUtility.getRSAPublicKeyFromJwk(jwkJsonString);
35+
}
36+
}

0 commit comments

Comments
 (0)