diff --git a/pom.xml b/pom.xml
index 70f0bc07..089bb7ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -304,7 +304,7 @@
com.cybersource
AuthenticationSdk
- 0.0.39
+ 0.0.40-SNAPSHOT
diff --git a/src/main/java/utilities/capturecontext/utility/CaptureContextParsingUtility.java b/src/main/java/utilities/capturecontext/utility/CaptureContextParsingUtility.java
new file mode 100644
index 00000000..7935b0f6
--- /dev/null
+++ b/src/main/java/utilities/capturecontext/utility/CaptureContextParsingUtility.java
@@ -0,0 +1,94 @@
+package utilities.capturecontext.utility;
+
+import com.cybersource.authsdk.cache.CacheForPublicKeys;
+import com.cybersource.authsdk.core.ConfigException;
+import com.cybersource.authsdk.core.MerchantConfig;
+import com.cybersource.authsdk.util.jwt.JWTUtility;
+import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwkException;
+import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwtException;
+import com.cybersource.authsdk.util.jwt.exceptions.JwtSignatureValidationException;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.SignedJWT;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.MalformedURLException;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.HashMap;
+
+public class CaptureContextParsingUtility {
+ private static CacheForPublicKeys cache = new CacheForPublicKeys();
+
+ public static JsonObject parseCaptureContextResponse(
+ String jwtValue, MerchantConfig merchantConfig, boolean verifyJwt)
+ throws InvalidJwtException, ConfigException, IOException, InvalidJwkException, JwtSignatureValidationException {
+ SignedJWT signedJWT = JWTUtility.parse(jwtValue);
+
+ if (verifyJwt) {
+ RSAPublicKey publicKey;
+ boolean isJwtValid = false;
+
+ JWSHeader jwsHeader = signedJWT.getHeader();
+
+ String kid = jwsHeader.getKeyID();
+
+ boolean isPublicKeyFromCache = false;
+
+ try {
+ publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
+ isPublicKeyFromCache = true;
+ } catch (NullPointerException e) {
+ fetchPublicKeyFromApi(kid, merchantConfig.getRunEnvironment());
+ publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
+ }
+
+ try {
+ assert publicKey != null;
+ isJwtValid = JWTUtility.verifyJwt(signedJWT, publicKey);
+ } catch (JwtSignatureValidationException e) {
+ if (isPublicKeyFromCache) {
+ fetchPublicKeyFromApi(kid, merchantConfig.getRunEnvironment());
+ publicKey = cache.getPublicKeyFromCache(merchantConfig.getRunEnvironment(), kid);
+ isJwtValid = JWTUtility.verifyJwt(signedJWT, publicKey);
+ }
+ }
+
+ if (!isJwtValid) {
+ throw new JwtSignatureValidationException("JWT validation failed");
+ }
+ }
+
+ return convertPayloadMapToJsonObject(signedJWT.getPayload());
+ }
+
+ private static JsonObject convertPayloadMapToJsonObject(Payload payload) {
+ Gson gson = new Gson();
+ Type typeObject = new TypeToken>() {}.getType();
+ String jsonRepresentation = gson.toJson(payload.toJSONObject(), typeObject);
+ return JsonParser.parseString(jsonRepresentation).getAsJsonObject();
+ }
+
+ private static void fetchPublicKeyFromApi(String kid, String runEnvironment) throws ConfigException, IOException, InvalidJwkException {
+ RSAPublicKey publicKey;
+
+ try {
+ publicKey = PublicKeyApiController.fetchPublicKey(kid, runEnvironment);
+ cache.addPublicKeyToCache(runEnvironment, kid, publicKey);
+ } catch (MalformedURLException err) {
+ throw new ConfigException("Invalid Runtime URL in Merchant Config");
+ } catch (IOException err) {
+ throw new IOException("Error while trying to retrieve public key from server");
+ } catch (ParseException err) {
+ throw new InvalidJwkException("JWK received from server cannot be parsed correctly", err);
+ } catch (JOSEException err) {
+ throw new InvalidJwkException("Cannot convert JWK to RSA Public Key", err);
+ }
+ }
+}
diff --git a/src/main/java/utilities/capturecontext/utility/PublicKeyApiController.java b/src/main/java/utilities/capturecontext/utility/PublicKeyApiController.java
new file mode 100644
index 00000000..2402b6b3
--- /dev/null
+++ b/src/main/java/utilities/capturecontext/utility/PublicKeyApiController.java
@@ -0,0 +1,36 @@
+package utilities.capturecontext.utility;
+
+import com.cybersource.authsdk.util.jwt.JWTUtility;
+import com.cybersource.authsdk.util.jwt.exceptions.InvalidJwkException;
+import com.nimbusds.jose.JOSEException;
+import okhttp3.Call;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+
+public class PublicKeyApiController {
+ public static RSAPublicKey fetchPublicKey(String kid, String runEnvironment)
+ throws IOException, ParseException, JOSEException, InvalidJwkException {
+ URL url = new URL("https://" + runEnvironment + "/flex/v2/public-keys/" + kid);
+
+ Request request = new Request.Builder().url(url).build();
+
+ OkHttpClient client = new OkHttpClient();
+
+ Call call = client.newCall(request);
+
+ String jwkJsonString;
+
+ try (Response response = call.execute()) {
+ assert response.body() != null;
+ jwkJsonString = response.body().string();
+ }
+
+ return JWTUtility.getRSAPublicKeyFromJwk(jwkJsonString);
+ }
+}