
com.cybersource.flex.sdk.FlexServiceImpl Maven / Gradle / Ivy
package com.cybersource.flex.sdk;
import com.cybersource.flex.sdk.impl.DigestHelper;
import com.cybersource.flex.sdk.impl.HttpResponse;
import com.cybersource.flex.sdk.internal.SHA256HMAC;
import com.cybersource.flex.sdk.internal.SecurityHelper;
import com.cybersource.flex.sdk.model.LongTermKey;
import com.cybersource.flex.sdk.repackaged.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.*;
import static com.cybersource.flex.sdk.internal.Constants.*;
public final class FlexServiceImpl implements FlexService {
private final Credentials credentials;
private final HttpClient httpClient;
FlexServiceImpl(final HttpClient httpClient, final Credentials credentials) {
this.httpClient = httpClient;
this.credentials = credentials;
}
@Override
public CaptureContext fromJwt(final String cc) {
return CaptureContextImpl.fromString(this, cc, null, System.currentTimeMillis());
}
@Override
public TransientToken fromJwt(final String cc, final String tt) {
final CaptureContext captureContext = fromJwt(cc);
return TransientToken.parse(captureContext.getPublicKey(), tt);
}
@Override
public PublicKey flexPublicKey(String kid) {
return LongTermKey.get(kid);
// TODO: (in about a year...) if publicKey == null --> try to retrieve and cache long living key
}
@Override
public CaptureContext createCaptureContext(final CaptureContextIntent intent) {
if (credentials instanceof Credentials.NoAuthentication) {
throw new IllegalStateException("This FlexService instance does not allow authenticated calls.");
}
final long startTime = System.currentTimeMillis();
try {
final String body = requestBodyFor(intent);
final Map headers = headersFor(host(credentials), path(credentials), getDigest(body), mid(credentials), kid(credentials), sharedSecret(credentials));
final HttpResponse response = httpClient.post(keysEndpoint(credentials), headers, body);
if (!response.isErrorResponse()) {
DigestHelper.verifyResponseDigest(response);
return CaptureContextImpl.fromHttpResponse(this, response, startTime);
} else {
DigestHelper.verifyResponseDigest(response, false); // CGK/Akamai errors have no Digest header.
throw ToolsV1.handleErrorResponse(response, startTime); // handle HTTP errors
}
} catch (IOException e) { // handle network level issues
String msg = String.format("IO exception (%s, %s) when creating Capture Context", e.getClass(), e.getMessage());
throw new FlexException.FlexIOException(msg, e, startTime);
}
}
@Override
public TransientToken createTransientToken(CaptureContext captureContext, Map map) {
final long startTime = System.currentTimeMillis();
final PublicKey publicKey = captureContext.getPublicKey();
final String flexOrigin = captureContext.getFlexOrigin();
final String tokensPath = captureContext.getTokensPath();
final String jwe = captureContext.jwe(map);
return createTransientToken(flexOrigin, tokensPath, publicKey, jwe, startTime);
// in future consider below:
// if (!getId().equals(transientToken.getCaptureContextId())) {
// throw new IllegalStateException("Transient token capture context does not match this capture context.");
// }
}
private TransientToken createTransientToken(final String origin, final String tokensPath, final PublicKey publicKey, final String jwe, long startTime) {
try {
final String reqBody = "{\"keyId\":\"" + jwe + "\"}"; // no escaping, as JWE string was previously validated.
final HttpResponse response = httpClient.post(origin + tokensPath, Collections.emptyMap(), reqBody);
DigestHelper.verifyResponseDigest(response, false);
if (!response.isErrorResponse()) {
return TransientTokenImpl.fromHttpResponse(publicKey, response, startTime);
} else {
throw ToolsV1.handleErrorResponse(response, startTime); // handle HTTP errors
}
} catch (IOException e) {
String msg = String.format("IO exception (%s, %s) when creating Flexible Transient Token", e.getClass(), e.getMessage());
throw new FlexException.FlexIOException(msg, e, startTime);
}
}
private static Map headersFor(String host, String path, String digest, String mid, String kid, byte[] secret) {
final Map headers = new LinkedHashMap<>();
headers.put(HTTP_REQHDR_HOST, host);
headers.put(HTTP_REQHDR_DATE, date());
headers.put(HTTP_REQHDR_REQUEST_TARGET, "post " + path);
headers.put(HTTP_REQHDR_DIGEST, digest);
headers.put(HTTP_REQHDR_MIDHEADER, mid);
headers.put(HTTP_REQHDR_SIGNATURE, generateSignature(headers, kid, secret));
headers.remove(HTTP_REQHDR_REQUEST_TARGET);
return headers;
}
private static String requestBodyFor(CaptureContextIntent intent){
final JSONObject payload = new JSONObject();
payload.put("encryptionType", "RsaOaep");
payload.put("targetOrigin", targetOrigins(intent.getTargetOrigins()));
return payload.toString();
}
private static String targetOrigins(final Set targetOrigins) {
final StringBuilder retVal = new StringBuilder();
targetOrigins.forEach(o -> {retVal.append(' '); retVal.append(o);});
if (retVal.length() > 0) {
retVal.deleteCharAt(0);
}
return retVal.toString();
}
private static String mid(Credentials credentials) {
return ((CredentialsImpl) credentials).getMid();
}
private static String kid(Credentials credentials) {
return ((CredentialsImpl) credentials).getKeyId();
}
private static byte[] sharedSecret(Credentials credentials) {
return ((CredentialsImpl) credentials).getSharedSecret().getData();
}
private static String keysEndpoint(Credentials credentials) {
Credentials.Environment environment = credentials.getEnvironment();
return environment.getUrl();
}
private static String host(Credentials credentials) {
Credentials.Environment environment = credentials.getEnvironment();
return environment.getHost();
}
private static String path(Credentials credentials) {
Credentials.Environment environment = credentials.getEnvironment();
return environment.getPath();
}
private static String date() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat.format(calendar.getTime());
}
private static String getDigest(String body) {
MessageDigest digester = SecurityHelper.getSha256Digester();
byte[] digest = digester.digest(body.getBytes(StandardCharsets.UTF_8));
return String.format("SHA-256=%s", Base64.getEncoder().encodeToString(digest));
}
public static String generateSignature(Map headers, final String keyId, final byte[] sharedSecret) {
try {
final SHA256HMAC sha256hmac = new SHA256HMAC(sharedSecret);
final StringBuilder signatureString = new StringBuilder();
final StringBuilder headersString = new StringBuilder();
for (Map.Entry e : headers.entrySet()) {
signatureString.append('\n').append(e.getKey()).append(": ").append(e.getValue());
headersString.append(' ').append(e.getKey());
}
signatureString.delete(0, 1);
headersString.delete(0, 1);
final StringBuilder signature = new StringBuilder();
sha256hmac.update(signatureString.toString().getBytes(StandardCharsets.UTF_8));
final byte[] hashBytes = sha256hmac.digest();
signature.append("keyid=\"").append(keyId).append("\", ")
.append("algorithm=\"HmacSHA256\", ")
.append("headers=\"").append(headersString).append("\", ")
.append("signature=\"").append(Base64.getEncoder().encodeToString(hashBytes)).append('\"');
return signature.toString();
} finally {
SecurityHelper.randomize(sharedSecret);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy