DummySslContext.java

/*-
 * #%L
 * io.earcam.utilitarian.net
 * %%
 * Copyright (C) 2017 earcam
 * %%
 * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
 *
 * You <b>must</b> choose to accept, in full - any individual or combination of
 * the following licenses:
 * <ul>
 * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
 * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
 * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
 * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
 * </ul>
 * #L%
 */
package io.earcam.utilitarian.net.ssl;

import static io.earcam.utilitarian.net.ssl.KeyManagers.keyManagerDummy;
import static io.earcam.utilitarian.net.ssl.KeyManagers.keyManagerSunX509;
import static io.earcam.utilitarian.net.ssl.NoopTrustManager.noopTrustManager;
import static io.earcam.utilitarian.security.KeyStores.keyStore;
import static io.earcam.utilitarian.security.Keys.rsa;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;

import io.earcam.unexceptional.Exceptional;
import io.earcam.unexceptional.UncheckedSecurityException;
import io.earcam.utilitarian.security.Certificates;

/**
 * <h1>Use for testing <b>only</b></h1> - otherwise why bother? Be honest about in/security
 */
public final class DummySslContext {

	private static final String PROTOCOL_SSL_V3 = "SSLv3";

	private static final class DummyHostnameVerifier implements HostnameVerifier {

		private final boolean always;


		DummyHostnameVerifier(boolean always)
		{
			this.always = always;
		}


		@Override
		public boolean verify(String hostname, SSLSession session)
		{
			return always;
		}

	}

	public static final HostnameVerifier ALWAYS_PASS_HOSTNAME_VERIFIER = new DummyHostnameVerifier(true);
	public static final HostnameVerifier ALWAYS_FAIL_HOSTNAME_VERIFIER = new DummyHostnameVerifier(true);


	private DummySslContext()
	{}


	/**
	 * Generates a permissive {@link SSLContext} with generated (in-memory) self-signed X509Certificate
	 *
	 * <p>
	 * <b>USE FOR TESTING ONLY</b>
	 * </p>
	 *
	 * @param host The hostname/IP
	 * @return a configured SSLContext
	 * @throws UncheckedSecurityException
	 * @throws UncheckedIOException
	 */
	public static SSLContext serverSslContext(String host)
	{
		return Exceptional.apply(DummySslContext::serverSslContext, host, new char[] { 'p', 'a', 's', 't', 'W', 'e', 'i', 'r', 'd' });
	}


	/**
	 * Generates a permissive {@link SSLContext} with generated (in-memory) self-signed X509Certificate
	 *
	 * <p>
	 * <b>USE FOR TESTING ONLY</b>
	 * </p>
	 *
	 * @param host The hostname/IP
	 * @param password the password to use for both keystore and certificate alias
	 * @return a configured SSLContext
	 * @throws KeyStoreException
	 * @throws CertificateException
	 * @throws IOException
	 * @throws GeneralSecurityException
	 */
	public static SSLContext serverSslContext(String host, char[] password) throws GeneralSecurityException
	{
		KeyPair pair = rsa();
		X509Certificate x509 = Certificates.certificate(pair, "DN=" + host + ", L=London, C=GB").toX509();
		KeyStore keyStore = keyStore("alias", password, pair, x509);

		SSLContext sslContext = SSLContext.getInstance(PROTOCOL_SSL_V3);
		sslContext.init(keyManagerSunX509(keyStore, password), noopTrustManager(), new SecureRandom());
		return sslContext;
	}


	/**
	 *
	 * @return a permissive (SSLv3 supporting TLSv1) SSLContext for client use
	 *
	 * @see #unverifiedConnection(String)
	 * @see #unverifiedConnection(URL)
	 */
	public static SSLContext unverifiedClientSslContext()
	{
		return Exceptional.apply(DummySslContext::unverifiedClientSslContext, PROTOCOL_SSL_V3);
	}


	public static SSLContext unverifiedClientSslContext(String sslProtocol) throws NoSuchAlgorithmException, KeyManagementException
	{
		SSLContext context = SSLContext.getInstance(sslProtocol);
		initialiseUnverified(context);
		return context;
	}


	private static void initialiseUnverified(SSLContext context) throws KeyManagementException
	{
		context.init(keyManagerDummy(), noopTrustManager(), new SecureRandom());
	}


	public static HostnameVerifier dummyHostnameVerifier()
	{
		return ALWAYS_PASS_HOSTNAME_VERIFIER;
	}


	public static HttpsURLConnection unverifiedConnection(String httpsUrl)
	{
		URL url = Exceptional.url(httpsUrl);
		return Exceptional.apply(DummySslContext::unverifiedConnection, url);
	}


	public static HttpsURLConnection unverifiedConnection(URL httpsUrl) throws IOException
	{
		HttpsURLConnection connection = (HttpsURLConnection) httpsUrl.openConnection();
		connection.setHostnameVerifier(dummyHostnameVerifier());
		connection.setSSLSocketFactory(unverifiedClientSslContext().getSocketFactory());
		return connection;
	}


	public static byte[] unverifiedResponse(String httpsUrl) throws IOException
	{
		HttpsURLConnection connection = unverifiedConnection(httpsUrl);
		connection.connect();
		InputStream input = connection.getInputStream();

		ByteArrayOutputStream output = new ByteArrayOutputStream();
		int b;
		while((b = input.read()) != -1) {
			output.write(b);
		}
		return output.toByteArray();
	}


	public static void enableSslDebug()
	{
		System.setProperty("javax.net.debug", "ssl:all");
	}
}