Here is the class that am using ,based on the flow that I have ,the validation always fails and return false in the line boolean isValid = signature.verify(signatureBytes); LOG.info("Signature valid? {}", isValid); can i plase request you to validate and advie what is it that am doing wrong and i am trying to use only java libraries for validations or conversions. Am not sure if am using right libs / code to vlaidate the token.
package com.wwg.integrations.validator;
import de.hybris.platform.servicelayer.config.ConfigurationService;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import javax.net.ssl.SSLContext;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
public class CaptureContextTokenValidator {
private static final Logger LOG = LoggerFactory.getLogger(WWGCaptureContextTokenValidator.class);
public static final String CYBERSOURCE_PUBLIC_KEY_URL = "cybersource.public.key.url";
private ConfigurationService configurationService;
public boolean validateCaptureContextToken(String captureContextToken) {
if (captureContextToken == null || captureContextToken.isEmpty()) {
LOG.error("Capture context token is null or empty");
return false;
}
String kidId = extractKidFromJWTToken(captureContextToken);
JSONObject jwkJson = fetchRSAKeyUsingKidId(kidId);
try {
PublicKey publicKey = extractPublicKeyFromJWK(jwkJson);
LOG.info("Public Key: " + publicKey);
return validateCaptureContextWithPublicKey(captureContextToken, publicKey);
} catch (Exception e) {
throw new RuntimeException("Error during validation", e);
}
}
private JSONObject fetchRSAKeyUsingKidId(String kidId) {
RestTemplate restTemplate = getRestTemplate();
final String publicKeyURL = getConfigurationService().getConfiguration().getString(CYBERSOURCE_PUBLIC_KEY_URL) + kidId;
LOG.info("Fetching public key using : " + publicKeyURL);
try {
URI uri = new URI(publicKeyURL);
HttpEntity<String> requestEntity = new HttpEntity<>(null, null);
ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, String.class);
String jsonResponse = result.getBody();
LOG.info("Response from public key fetch: " + jsonResponse);
return new JSONObject(jsonResponse);
} catch (URISyntaxException e) {
throw new RuntimeException("Invalid URI", e);
} catch (Exception e) {
throw new RuntimeException("Error parsing JSON response", e);
}
}
private RestTemplate getRestTemplate() {
try {
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, (X509Certificate[] chain, String authType) -> true)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(HttpClients.custom().setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext)).build());
return new RestTemplate(requestFactory);
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
throw new RuntimeException("Error creating RestTemplate", e);
}
}
public PublicKey extractPublicKeyFromJWK(JSONObject jwk) throws Exception {
// Extract the modulus and exponent
String n = jwk.getString("n");
String e = jwk.getString("e");
// Add padding to make Base64 decoding safe
n = addBase64Padding(n);
e = addBase64Padding(e);
// Decode using URL-safe decoder
byte[] modulusBytes = Base64.getUrlDecoder().decode(n);
byte[] exponentBytes = Base64.getUrlDecoder().decode(e);
// Convert to BigInteger
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger exponent = new BigInteger(1, exponentBytes);
// Create the public key spec
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
private String addBase64Padding(String base64) {
int paddingLength = (4 - base64.length() % 4) % 4;
return base64 + "=".repeat(paddingLength);
}
private boolean validateCaptureContextWithPublicKey(String captureContextToken, PublicKey publicKey) {
try {
// Split the JWT token into parts
String[] parts = captureContextToken.split("\\.");
if (parts.length != 3) {
LOG.error("Invalid JWT token format: {}", captureContextToken);
return false;
}
// Construct header and payload
String headerAndPayload = parts[0] + "." + parts[1];
byte[] signatureBytes = Base64.getUrlDecoder().decode(parts[2]);
// Initialize the Signature instance
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
// Update the Signature instance with header and payload
signature.update(headerAndPayload.getBytes(StandardCharsets.US_ASCII));
// Verify the signature
boolean isValid = signature.verify(signatureBytes);
LOG.info("Signature valid? {}", isValid);
return isValid;
} catch (Exception e) {
LOG.error("Error validating JWT signature", e);
return false;
}
}
private String extractKidFromJWTToken(String jwt) {
try {
String[] parts = jwt.split("\\.");
if (parts.length < 2) {
throw new IllegalArgumentException("Invalid JWT token format");
}
String bodyData = new String(Base64.getUrlDecoder().decode(parts[1]));
JSONObject jsonObject = new JSONObject(bodyData);
return jsonObject
.getJSONObject("flx")
.getJSONObject("jwk")
.optString("kid");
} catch (Exception e) {
throw new RuntimeException("Failed to extract kid from JWT token", e);
}
}
public ConfigurationService getConfigurationService() {
return configurationService;
}
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
}
