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

com.paypal.base.SSLUtil Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
package com.paypal.base;

import com.paypal.base.codec.binary.Base64;
import com.paypal.base.exception.SSLConfigurationException;
import com.paypal.base.rest.PayPalRESTException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

/**
 * Class SSLUtil
 *
 */
public abstract class SSLUtil {

	private static final Logger log = LoggerFactory.getLogger(SSLUtil.class);

	/**
	 * KeyManagerFactory used for {@link SSLContext} {@link KeyManager}
	 */
	private static final KeyManagerFactory KMF;

	/**
	 * Private {@link Map} used for caching {@link KeyStore}s
	 */
	private static final Map STOREMAP;

	/**
	 * Map used for dynamic configuration
	 */
	private static final Map CONFIG_MAP;

	static {
		try {

			// Initialize KeyManagerFactory and local KeyStore cache
			KMF = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
			STOREMAP = new HashMap();
			CONFIG_MAP = SDKUtil.combineDefaultMap(ConfigManager
					.getInstance().getConfigurationMap());
		} catch (NoSuchAlgorithmException e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	/**
	 * Returns a SSLContext
	 *
	 * @param keymanagers
	 *            KeyManager[] The key managers
	 * @return SSLContext with proper client certificate
	 * @throws SSLConfigurationException
	 */
	public static SSLContext getSSLContext(KeyManager[] keymanagers)
			throws SSLConfigurationException {
		try {
			SSLContext ctx = null;
			String protocol = CONFIG_MAP.get(Constants.SSLUTIL_PROTOCOL);
			try {
				ctx = SSLContext.getInstance("TLSv1.2");
			} catch (NoSuchAlgorithmException e) {
				log.warn("WARNING: Your system does not support TLSv1.2. Per PCI Security Council mandate (https://github.com/paypal/TLS-update), you MUST update to latest security library.");
				ctx = SSLContext.getInstance(protocol);
			}
			ctx.init(keymanagers, null, null);
			return ctx;
		} catch (Exception e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		}
	}

	/**
	 * Retrieves keyStore from the cached {@link Map}, if not present loads
	 * certificate into java keyStore and caches it for further references
	 *
	 * @param p12Path
	 *            Path to the client certificate
	 * @param password
	 *            {@link KeyStore} password
	 * @return keyStore {@link KeyStore} loaded with the certificate
	 * @throws NoSuchProviderException
	 * @throws KeyStoreException
	 * @throws CertificateException
	 * @throws NoSuchAlgorithmException
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	private static KeyStore p12ToKeyStore(String p12Path, String password)
			throws NoSuchProviderException, KeyStoreException,
			CertificateException, NoSuchAlgorithmException, IOException {
		KeyStore keyStore = STOREMAP.get(p12Path);
		if (keyStore == null) {
			keyStore = KeyStore.getInstance("PKCS12", CONFIG_MAP.get(Constants.SSLUTIL_JRE));
			FileInputStream in = null;
			try {
				in = new FileInputStream(p12Path);
				keyStore.load(in, password.toCharArray());
				STOREMAP.put(p12Path, keyStore);
			} finally {
				if (in != null) {
					in.close();
				}
			}
		}
		return keyStore;
	}

	/**
	 * Create a SSLContext with provided client certificate
	 *
	 * @param certPath
	 * @param certPassword
	 * @return SSLContext
	 * @throws SSLConfigurationException
	 */
	public static SSLContext setupClientSSL(String certPath, String certPassword)
			throws SSLConfigurationException {
		SSLContext sslContext = null;
		try {
			KeyStore ks = p12ToKeyStore(certPath, certPassword);
			KMF.init(ks, certPassword.toCharArray());
			sslContext = getSSLContext(KMF.getKeyManagers());
		} catch (NoSuchAlgorithmException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		} catch (KeyStoreException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		} catch (UnrecoverableKeyException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		} catch (CertificateException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		} catch (NoSuchProviderException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		} catch (IOException e) {
			throw new SSLConfigurationException(e.getMessage(), e);
		}
		return sslContext;
	}

	/**
	 * Performs Certificate Chain Validation on provided certificates. The method verifies if the client certificates provided are generated from root certificates
	 * trusted by application.
	 *
	 * @param clientCerts Collection of X509Certificates provided in request
	 * @param trustCerts Collection of X509Certificates trusted by application
	 * @param authType Auth Type for Certificate
	 * @return true if client and server are chained together, false otherwise
	 * @throws PayPalRESTException
	 */
	public static boolean validateCertificateChain(Collection clientCerts, Collection trustCerts, String authType) throws PayPalRESTException  {
		TrustManager trustManagers[];
		X509Certificate[] clientChain;
		try {

			clientChain = clientCerts.toArray(new X509Certificate[0]);
			List list = Arrays.asList(clientChain);
			clientChain = list.toArray(new X509Certificate[0]);

			// Create a Keystore and load the Root CA Cert
			KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
			keyStore.load(null, "".toCharArray());

			// Iterate through each certificate and add to keystore
			int i = 0;
			for (Iterator payPalCertificate = trustCerts.iterator(); payPalCertificate.hasNext();) {
				X509Certificate x509Certificate = (X509Certificate) payPalCertificate.next();
				keyStore.setCertificateEntry("paypalCert" + i, x509Certificate);
				i++;
			}

			// Create TrustManager
			TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			trustManagerFactory.init(keyStore);
			trustManagers = trustManagerFactory.getTrustManagers();

		} catch (Exception ex) {
			throw new PayPalRESTException(ex);
		}

		// For Each TrustManager of type X509
		for(TrustManager trustManager : trustManagers) {
			if(trustManager instanceof X509TrustManager) {
				X509TrustManager pkixTrustManager = (X509TrustManager) trustManager;
				// Check the trust manager if server is trusted
				try {
					pkixTrustManager.checkClientTrusted(clientChain, (authType == null || authType == "") ? "RSA" : authType);
					// Checks that the certificate is currently valid. It is if the current date and time are within the validity period given in the certificate.
					for (X509Certificate cert : clientChain) {
						cert.checkValidity();
						// Check for CN name matching
						String dn = cert.getSubjectX500Principal().getName();
						String[] tokens = dn.split(",");
						boolean hasPaypalCn = false;

						for (String token: tokens) {
							if (token.startsWith("CN=messageverificationcerts") && token.endsWith(".paypal.com")) {
								hasPaypalCn = true;
							}
						}

						if (!hasPaypalCn) {
							throw new PayPalRESTException("CN of client certificate does not match with trusted CN");
						}
					}
					// If everything looks good, return true
					return true;
				} catch (CertificateException e) {
					throw new PayPalRESTException(e);
				}
			}
		}


		return false;

	}

	/**
	 * Downloads Certificate from URL
	 * @deprecated Please use {@link #downloadCertificateFromPath(String, Map)} instead.
	 *
	 * @param urlPath
	 * @return InputStream containing certificate data
	 * @throws PayPalRESTException
	 */
	public static InputStream downloadCertificateFromPath(String urlPath) throws PayPalRESTException {
		return downloadCertificateFromPath(urlPath, ConfigManager.getDefaultSDKMap());
	}

	/**
	 * Downloads Certificate from URL
	 *
	 * @param urlPath
	 * @param configurations Map of configurations.
	 * @return InputStream containing certificate data
	 * @throws PayPalRESTException
	 */
	public static InputStream downloadCertificateFromPath(String urlPath, Map configurations) throws PayPalRESTException {
		if (urlPath == null || urlPath.trim() == "") {
			throw new PayPalRESTException("Certificate Path cannot be empty");
		}
		try {
			Map headerMap = new HashMap();
			HttpConfiguration httpConfiguration = new HttpConfiguration();
			httpConfiguration.setEndPointUrl(urlPath);
			httpConfiguration.setConnectionTimeout(Integer
					.parseInt(configurations.get(Constants.HTTP_CONNECTION_TIMEOUT)));
			httpConfiguration.setMaxRetry(Integer.parseInt(configurations.get(Constants.HTTP_CONNECTION_RETRY)));
			httpConfiguration.setReadTimeout(Integer.parseInt(configurations.get(Constants.HTTP_CONNECTION_READ_TIMEOUT)));
			httpConfiguration.setMaxHttpConnection(Integer
					.parseInt(configurations.get(Constants.HTTP_CONNECTION_MAX_CONNECTION)));
			httpConfiguration.setHttpMethod("GET");
			HttpConnection connection = ConnectionManager.getInstance()
					.getConnection();
			connection.createAndconfigureHttpConnection(httpConfiguration);
			URL url = new URL(urlPath);
			headerMap.put("Host", url.getHost());
			return connection.executeWithStream(url.toString(), "", headerMap);
		} catch (Exception ex) {
			throw new PayPalRESTException(ex);
		}
	}

	/**
	 * Generate Collection of Certificate from Input Stream
	 *
	 * @param stream InputStream of Certificate data
	 * @return Collection
	 * @throws PayPalRESTException
	 */
	@SuppressWarnings("unchecked")
	public static Collection getCertificateFromStream(InputStream stream) throws PayPalRESTException {
		if (stream == null) { throw new PayPalRESTException("Certificate Not Found"); }
		Collection certs = null;
		try {
			// Create a Certificate Factory
			CertificateFactory cf = CertificateFactory.getInstance("X.509");

			// Read the Trust Certs
			certs = (Collection) cf.generateCertificates(stream);
		} catch (CertificateException ex) {
			throw new PayPalRESTException(ex);
		}
		return certs;
	}

	/**
	 * Generates a CRC 32 Value of String passed
	 *
	 * @param data
	 * @return long crc32 value of input. -1 if string is null
	 * @throws RuntimeException if UTF-8 is not a supported character set
	 */
	public static long crc32(String data) {
		if (data == null) {
			return -1;
		}

		try {
			// get bytes from string
			byte bytes[] = data.getBytes("UTF-8");
			Checksum checksum = new CRC32();
			// update the current checksum with the specified array of bytes
			checksum.update(bytes, 0, bytes.length);
			// get the current checksum value
			return checksum.getValue();
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Validates Webhook Signature validation based on https://developer.paypal.com/docs/integration/direct/rest-webhooks-overview/#event-signature
	 * Returns true if signature is valid
	 *
	 * @param clientCerts Client Certificates
	 * @param algo Algorithm used for signature creation by server
	 * @param actualSignatureEncoded  Paypal-Transmission-Sig header value passed by server
	 * @param expectedSignature Signature generated by formatting data with CRC32 value of request body
	 * @param requestBody Request body from server
	 * @param webhookId Id for PayPal Webhook created for receiving the data
	 * @return true if signature is valid, false otherwise
	 *
	 * @throws NoSuchAlgorithmException
	 * @throws SignatureException
	 * @throws InvalidKeyException
	 */
	public static Boolean validateData(Collection clientCerts, String algo,
			String actualSignatureEncoded, String expectedSignature, String requestBody, String webhookId) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
		// Get the signatureAlgorithm from the PAYPAL-AUTH-ALGO HTTP header
		Signature signatureAlgorithm = Signature.getInstance(algo);
		// Get the certData from the URL provided in the HTTP headers and cache it
		X509Certificate[] clientChain = clientCerts.toArray(new X509Certificate[0]);
		signatureAlgorithm.initVerify(clientChain[0].getPublicKey());
		signatureAlgorithm.update(expectedSignature.getBytes());
		// Actual signature is base 64 encoded and available in the HTTP headers
		byte[] actualSignature = Base64.decodeBase64(actualSignatureEncoded.getBytes());
		boolean isValid = signatureAlgorithm.verify(actualSignature);
		return isValid;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy