/*
 * Copyright 2011 Federal Chancellery Austria and
 * Graz University of Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.gv.util.ssl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import at.gv.util.TrustAllManager;
import at.gv.util.ex.EgovUtilException;

/**
 * Some utility methods for SSL handling.
 * 
 * @author <a href="mailto:Arne.Tauber@egiz.gv.at">Arne Tauber</a>
 *
 */
public class SSLUtils {

	private static Logger log = LoggerFactory.getLogger(SSLUtils.class);
	
	public static boolean useLaxHostNameVerifier(Properties props, String propsPrefix) {
		try {
			return Boolean.parseBoolean(props.getProperty(propsPrefix + ".ssl.laxhostnameverification"));
		} catch(Exception e) {
			return false;
		}
  }
	
	public static SSLContext getPropertiesSSLContext(Properties props, String configDir, String propsPrefix, boolean forceTrustAllManager) throws EgovUtilException {
		log.trace("Configuring SSL socket factory.");
		if (props == null) {
			throw new NullPointerException("Argument 'properties' must not be null.");
		}
		if (configDir == null) {
			throw new NullPointerException("Argument 'configDir' must not be null.");
		}
		boolean hasSSLConfigured = props.getProperty(propsPrefix + ".ssl.keystore.file") != null ||
							props.getProperty(propsPrefix + ".ssl.truststore.file") != null ||
							"true".equalsIgnoreCase(props.getProperty(propsPrefix + ".ssl.trustall"));
		
		log.trace("SSL enabled: " + hasSSLConfigured);
		if (hasSSLConfigured) {
			log.trace("Configuring using standard JKS or PKCS12 keystore/truststore.");
			try {
	      SSLContext context = SSLContext.getInstance("TLS");

	      // load client key store
	      KeyManager[] keyManager = null;
		    if (props.getProperty(propsPrefix + ".ssl.keystore.file") != null) {
		    	log.trace("Keystore definition found.");
		      String keyStoreFileName = props.getProperty(propsPrefix + ".ssl.keystore.file");
		      File keyTmp = new File(keyStoreFileName);
		      File keyStoreFile = null;
		      if (keyTmp.isAbsolute()) {
		      	keyStoreFile = keyTmp;
		      } else {
		      	keyStoreFile = new File(configDir, keyStoreFileName);
		      }
		      log.trace("Key store location: " + keyStoreFile);
		      String keyStorePassword = props.getProperty(propsPrefix + ".ssl.keystore.password");
		      if (keyStorePassword == null) {
		      	log.trace("No keystore password set in configuration, using empty password.");
		      	keyStorePassword = "";
		      }
		      String keyStoreType = props.getProperty(propsPrefix + ".ssl.keystore.type");
		      if (keyStoreType == null) {
		      	log.trace("No keystore type set in configuration, using default JKS.");
		      	keyStoreType = "JKS";
		      }
		      log.trace("Key store type: " + keyStoreType);
		      KeyStore clientStore = KeyStore.getInstance(keyStoreType);
		      clientStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
		      KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
		      kmf.init(clientStore, keyStorePassword.toCharArray());
		      keyManager = kmf.getKeyManagers();
		    }	else {
		    	log.trace("No keystore definition found. Not using SSL client authentication.");
		    }
	      // load trust store
	      TrustManager[] trustManager = null;
	      if (forceTrustAllManager || "true".equalsIgnoreCase(props.getProperty(propsPrefix + ".ssl.trustall"))) {
	      	log.info("Trust all property is switched on. This setting is not recommended.");
	      	trustManager = new TrustManager[] { new TrustAllManager() };
	      } else if (props.getProperty(propsPrefix + ".ssl.truststore.file") != null) {
	      	log.trace("Using standard trust store mechanism (truststore file definition found).");
	      	String trustStoreFileName = props.getProperty(propsPrefix + ".ssl.truststore.file");
		      File trustTmp = new File(trustStoreFileName);
		      File trustStoreFile = null;
		      if (trustTmp.isAbsolute()) {
		      	trustStoreFile = trustTmp;
		      } else {
		      	trustStoreFile = new File(configDir, trustStoreFileName);
		      }
	      	log.trace("Trust store file location: " + trustStoreFile);
	      	if (trustStoreFile == null) {
	      		throw new EgovUtilException("Please set a trust store in your configuration or switch the trust all property on.");
	      	}
	      	String trustStorePassword = props.getProperty(propsPrefix + ".ssl.truststore.password");
	      	if (trustStorePassword == null) {
		      	log.trace("No truststore password set in configuration, using empty password.");
		      	trustStorePassword = "";
		      }
	      	String trustStoreType = props.getProperty(propsPrefix + ".ssl.truststore.type");
		      if (trustStoreType == null) {
		      	log.trace("No truststore type set in configuration, using default JKS.");
		      	trustStoreType = "JKS";
		      }
		      log.trace("Trust store type: " + trustStoreType);
		      KeyStore trustStore = KeyStore.getInstance(trustStoreType);
		      trustStore.load(new FileInputStream(trustStoreFile), trustStorePassword.toCharArray());
		      TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
		      tmf.init(trustStore);
		      trustManager = tmf.getTrustManagers();
	      } else {
	      	log.warn("No truststore definition found. Using standard Java truststore mechanism.");
	      }
	      context.init(keyManager, trustManager, new SecureRandom());
	      return context;
      } catch (NoSuchAlgorithmException e) {
	      throw new EgovUtilException(e);
      } catch (KeyStoreException e) {
      	throw new EgovUtilException(e);
      } catch (CertificateException e) {
      	throw new EgovUtilException(e);
      } catch (FileNotFoundException e) {
      	throw new EgovUtilException(e);
      } catch (IOException e) {
      	throw new EgovUtilException(e);
      } catch (UnrecoverableKeyException e) {
      	throw new EgovUtilException(e);
      } catch (KeyManagementException e) {
      	throw new EgovUtilException(e);
      }
		} else {
			throw new EgovUtilException("Please provide an SSL configuration in your properties file.");
		}
	}

	
}