hps / heartland-java

Heartland Payment Systems Java SDK
10 stars 11 forks source link

Update HpsTokenService to support SSL pinning #7

Open anokmik opened 6 years ago

anokmik commented 6 years ago

Hello,

Please, update HpsTokenService to support SSL pinning. Updated implementation is provided below.

Thanks!

public class HpsTokenService {

    private String mPublicKey;
    private String mCertificateBase64EncodedPublicKey;
    private String mUrl;

    public HpsTokenService(String publicKey, String certificateBase64EncodedPublicKey) {
        mPublicKey = publicKey;
        mCertificateBase64EncodedPublicKey = certificateBase64EncodedPublicKey;

        if (publicKey == null) {
            throw new IllegalArgumentException("publicKey can not be null");
        }

        String[] components = mPublicKey.split("_");

        if (components.length < 3) {
            throw new IllegalArgumentException("publicKey format invalid");
        }

        String env = components[1].toLowerCase();

        if (env.equals("prod")) {
            mUrl = "https://api2.heartlandportico.com/SecureSubmit.v1/api/token";
        } else {
            mUrl = "https://cert.api2.heartlandportico.com/Hps.Exchange.PosGateway.Hpf.v1/api/token";
        }
    }

    public HpsToken getToken(HpsCreditCard card) throws IOException {
        HttpsURLConnection conn = (HttpsURLConnection) new URL(mUrl).openConnection();
        HpsToken result = null;

        byte[] creds = String.format("%s:", mPublicKey).getBytes();
        String auth = String.format("Basic %s", Base64.encodeBase64URLSafeString(creds));

        Gson gson = new Gson();
        String payload = gson.toJson(new HpsToken(card));

        byte[] bytes = payload.getBytes();

        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setRequestMethod("POST");
        conn.addRequestProperty("Authorization", auth);
        conn.addRequestProperty("Content-Type", "application/json");
        conn.addRequestProperty("Content-Length", String.format("%s", bytes.length));

        conn.connect();

        if (isSslPinningSuccessful(conn)) {
            DataOutputStream requestStream = new DataOutputStream(conn.getOutputStream());
            requestStream.write(bytes);
            requestStream.flush();
            requestStream.close();

            try {
                InputStreamReader responseStream = new InputStreamReader(conn.getInputStream());
                result = gson.fromJson(responseStream, HpsToken.class);
                responseStream.close();
            } catch (IOException e) {
                if (conn.getResponseCode() == 400) {
                    InputStreamReader errorStream = new InputStreamReader(conn.getErrorStream());
                    result = gson.fromJson(errorStream, HpsToken.class);
                    errorStream.close();
                } else {
                    throw new IOException(e);
                }
            }
        }

        return result;
    }

    private boolean isSslPinningSuccessful(HttpsURLConnection conn) {
        try {
            Certificate[] certs = conn.getServerCertificates();
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            for (Certificate cert : certs) {
                X509Certificate x509Certificate = (X509Certificate) cert;
                byte[] encodedPublicKey = x509Certificate.getPublicKey().getEncoded();
                byte[] encodedPublicKeySha256Bytes = md.digest(encodedPublicKey);
                String encodedPublicKeyBase64String = Base64.encodeBase64URLSafeString(encodedPublicKeySha256Bytes);
                if (mCertificateBase64EncodedPublicKey.equals(encodedPublicKeyBase64String)) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

}

Best regards, Mikle Anokhin.