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); + } +}