/*
 Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
 

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA



 */
package com.mysql.jdbc;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.sql.SQLException;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Holds functionality that falls under export-control regulations.
 * 
 * @author Mark Matthews
 * 
 * @version $Id: ExportControlled.java,v 1.1.2.1 2005/05/13 18:58:38 mmatthews
 *          Exp $
 */
public class ExportControlled {
	private static final String SQL_STATE_BAD_SSL_PARAMS = "08000";

	protected static boolean enabled() {
		// we may wish to un-static-ify this class
		// this static method call may be removed entirely by the compiler
		return true;
	}

	/**
	 * Converts the socket being used in the given MysqlIO to an SSLSocket by
	 * performing the SSL/TLS handshake.
	 * 
	 * @param mysqlIO
	 *            the MysqlIO instance containing the socket to convert to an
	 *            SSLSocket.
	 * 
	 * @throws CommunicationsException
	 *             if the handshake fails, or if this distribution of
	 *             Connector/J doesn't contain the SSL crytpo hooks needed to
	 *             perform the handshake.
	 */
	protected static void transformSocketToSSLSocket(MysqlIO mysqlIO)
			throws SQLException {
		javax.net.ssl.SSLSocketFactory sslFact = getSSLSocketFactoryDefaultOrConfigured(mysqlIO);

		try {
			mysqlIO.mysqlConnection = sslFact.createSocket(
					mysqlIO.mysqlConnection, mysqlIO.host, mysqlIO.port, true);

			// need to force TLSv1, or else JSSE tries to do a SSLv2 handshake
			// which MySQL doesn't understand
			((javax.net.ssl.SSLSocket) mysqlIO.mysqlConnection)
					.setEnabledProtocols(new String[] { "TLSv1" }); //$NON-NLS-1$
			((javax.net.ssl.SSLSocket) mysqlIO.mysqlConnection)
					.startHandshake();

			if (mysqlIO.connection.getUseUnbufferedInput()) {
				mysqlIO.mysqlInput = mysqlIO.mysqlConnection.getInputStream();
			} else {
				mysqlIO.mysqlInput = new BufferedInputStream(
						mysqlIO.mysqlConnection.getInputStream(), 16384);
			}

			mysqlIO.mysqlOutput = new BufferedOutputStream(
					mysqlIO.mysqlConnection.getOutputStream(), 16384);

			mysqlIO.mysqlOutput.flush();
		} catch (IOException ioEx) {
			throw SQLError.createCommunicationsException(mysqlIO.connection,
					mysqlIO.getLastPacketSentTimeMs(), mysqlIO.getLastPacketReceivedTimeMs(),
					ioEx, mysqlIO.getExceptionInterceptor());
		}
	}

	private ExportControlled() { /* prevent instantiation */
	}

	private static SSLSocketFactory getSSLSocketFactoryDefaultOrConfigured(
			MysqlIO mysqlIO) throws SQLException {
		String clientCertificateKeyStoreUrl = mysqlIO.connection
				.getClientCertificateKeyStoreUrl();
		String trustCertificateKeyStoreUrl = mysqlIO.connection
				.getTrustCertificateKeyStoreUrl();
		String clientCertificateKeyStoreType = mysqlIO.connection
				.getClientCertificateKeyStoreType();
		String clientCertificateKeyStorePassword = mysqlIO.connection
				.getClientCertificateKeyStorePassword();
		String trustCertificateKeyStoreType = mysqlIO.connection
				.getTrustCertificateKeyStoreType();
		String trustCertificateKeyStorePassword = mysqlIO.connection
				.getTrustCertificateKeyStorePassword();

		if (StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl)
				&& StringUtils.isNullOrEmpty(trustCertificateKeyStoreUrl)) {
			if (mysqlIO.connection.getVerifyServerCertificate()) {
				return (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory
						.getDefault();
			}
		}

		TrustManagerFactory tmf = null;
		KeyManagerFactory kmf = null;

		try {
			tmf = TrustManagerFactory.getInstance(TrustManagerFactory
					.getDefaultAlgorithm());
			kmf = KeyManagerFactory.getInstance(KeyManagerFactory
					.getDefaultAlgorithm());
		} catch (NoSuchAlgorithmException nsae) {
			throw SQLError
					.createSQLException(
							"Default algorithm definitions for TrustManager and/or KeyManager are invalid.  Check java security properties file.",
							SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
		}

		if (!StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl)) {
			try {
				if (!StringUtils.isNullOrEmpty(clientCertificateKeyStoreType)) {
					KeyStore clientKeyStore = KeyStore
							.getInstance(clientCertificateKeyStoreType);
					URL ksURL = new URL(clientCertificateKeyStoreUrl);
					char[] password = (clientCertificateKeyStorePassword == null) ? new char[0]
							: clientCertificateKeyStorePassword.toCharArray();
					clientKeyStore.load(ksURL.openStream(), password);
					kmf.init(clientKeyStore, password);
				}
			} catch (UnrecoverableKeyException uke) {
				throw SQLError
						.createSQLException(
								"Could not recover keys from client keystore.  Check password?",
								SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (NoSuchAlgorithmException nsae) {
				throw SQLError.createSQLException(
						"Unsupported keystore algorithm [" + nsae.getMessage()
								+ "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (KeyStoreException kse) {
				throw SQLError.createSQLException(
						"Could not create KeyStore instance ["
								+ kse.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (CertificateException nsae) {
				throw SQLError.createSQLException("Could not load client"
						+ clientCertificateKeyStoreType + " keystore from "
						+ clientCertificateKeyStoreUrl, mysqlIO.getExceptionInterceptor());
			} catch (MalformedURLException mue) {
				throw SQLError.createSQLException(clientCertificateKeyStoreUrl
						+ " does not appear to be a valid URL.", SQL_STATE_BAD_SSL_PARAMS, 0,
						false, mysqlIO.getExceptionInterceptor());
			} catch (IOException ioe) {
				SQLException sqlEx = SQLError.createSQLException("Cannot open "
						+ clientCertificateKeyStoreUrl + " ["
						+ ioe.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
				sqlEx.initCause(ioe);
				
				throw sqlEx;
			}
		}

		if (!StringUtils.isNullOrEmpty(trustCertificateKeyStoreUrl)) {

			try {
				if (!StringUtils.isNullOrEmpty(trustCertificateKeyStoreType)) {
					KeyStore trustKeyStore = KeyStore
							.getInstance(trustCertificateKeyStoreType);
					URL ksURL = new URL(trustCertificateKeyStoreUrl);
	
					char[] password = (trustCertificateKeyStorePassword == null) ? new char[0]
							: trustCertificateKeyStorePassword.toCharArray();
					trustKeyStore.load(ksURL.openStream(), password);
					tmf.init(trustKeyStore);
				}
			} catch (NoSuchAlgorithmException nsae) {
				throw SQLError.createSQLException(
						"Unsupported keystore algorithm [" + nsae.getMessage()
								+ "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (KeyStoreException kse) {
				throw SQLError.createSQLException(
						"Could not create KeyStore instance ["
								+ kse.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (CertificateException nsae) {
				throw SQLError.createSQLException("Could not load trust"
						+ trustCertificateKeyStoreType + " keystore from "
						+ trustCertificateKeyStoreUrl, SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
			} catch (MalformedURLException mue) {
				throw SQLError.createSQLException(trustCertificateKeyStoreUrl
						+ " does not appear to be a valid URL.", SQL_STATE_BAD_SSL_PARAMS, 0,
						false, mysqlIO.getExceptionInterceptor());
			} catch (IOException ioe) {
				SQLException sqlEx = SQLError.createSQLException("Cannot open "
						+ trustCertificateKeyStoreUrl + " [" + ioe.getMessage()
						+ "]", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
				
				sqlEx.initCause(ioe);
				
				throw sqlEx;
			}
		}

		SSLContext sslContext = null;

		try {
			sslContext = SSLContext.getInstance("TLS");
			sslContext.init(StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl) ?  null : kmf.getKeyManagers(), mysqlIO.connection
					.getVerifyServerCertificate() ? tmf.getTrustManagers()
					: new X509TrustManager[] { new X509TrustManager() {
						public void checkClientTrusted(X509Certificate[] chain,
								String authType) {
							// return without complaint
						}

						public void checkServerTrusted(X509Certificate[] chain,
								String authType) throws CertificateException {
							// return without complaint
						}

						public X509Certificate[] getAcceptedIssuers() {
							return null;
						}
					} }, null);

			return sslContext.getSocketFactory();
		} catch (NoSuchAlgorithmException nsae) {
			throw SQLError.createSQLException("TLS"
					+ " is not a valid SSL protocol.",
					SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
		} catch (KeyManagementException kme) {
			throw SQLError.createSQLException("KeyManagementException: "
					+ kme.getMessage(), SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
		}
	}
}