All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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