/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 Sun Microsystems, Inc. All rights reserved. 
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License ("CDDL") (collectively, the "License").  You may
 * not use this file except in compliance with the License.  You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or mq/legal/LICENSE.txt.  See the License for the specific language
 * governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at mq/legal/LICENSE.txt.  Sun designates
 * this particular file as subject to the "Classpath" exception as provided by
 * Sun in the GPL Version 2 section of the License file that accompanied this
 * code.  If applicable, add the following below the License Header, with the
 * fields enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or  to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright holder. 
 */

/*
 * @(#)KeystoreUtil.java	1.4 06/29/07
 */ 

package com.sun.messaging.jmq.jmsserver.util;

import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.util.Properties;
import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
import com.sun.messaging.jmq.jmsserver.Broker;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.config.BrokerConfig;
import com.sun.messaging.jmq.jmsserver.resources.*;
import com.sun.messaging.jmq.jmsserver.net.tls.DefaultTrustManager;
import com.sun.messaging.jmq.util.StringUtil;
import com.sun.messaging.jmq.util.Password;

public class KeystoreUtil implements SSLPropertyMap {
    public final static String
        KEYSTORE_DIR_PROP          = Globals.IMQ + ".keystore.file.dirpath",
        KEYSTORE_FILE_PROP         = Globals.IMQ + ".keystore.file.name",
        KEYSTORE_PASSWORD_PROP     = Globals.IMQ + ".keystore.password";

    private static String keystore_location = null;
    private static String pass_phrase = null;

    protected static final BrokerResources br = Globals.getBrokerResources();

    public static void clear() {
        keystore_location = null;
        String pass_phrase = null;
    }

    public static String getKeystoreLocation() throws IOException {
	if (keystore_location == null)  {
	    BrokerConfig bcfg;
	    bcfg = Globals.getConfig();	    	    

            // Get Keystore Location and  Passphrase here .....
	    
            String dir, file, value, pf_dir, pf_file, pf_value, file_sep;
	    
            file_sep = System.getProperty("file.separator");
	    
            // Get Keystore location by getting the directory and the name
            // of the keystore file.

            if ((value = bcfg.getProperty(KEYSTORE_DIR_PROP)) != null) {
                value = StringUtil.expandVariables(value, bcfg);
                dir = value;
            } else {
                dir = bcfg.getProperty(Globals.IMQ + ".varhome") +
                file_sep + "security";
            }
	    
            keystore_location = dir + file_sep
			+ bcfg.getProperty(KEYSTORE_FILE_PROP);
	
	}

	return (keystore_location);
    }

    public static String getKeystorePassword() throws IOException {
	if (pass_phrase == null)  {
	    BrokerConfig bcfg;
       	    Password pw = null;

	    bcfg = Globals.getConfig();	    	    

            // Get Passphrase from property setting 
            pass_phrase = bcfg.getProperty(KEYSTORE_PASSWORD_PROP);
            // if passphrase is null then get it thro' user interaction
            int retry = 0;
            pw = new Password();
            while ((pass_phrase == null || pass_phrase.equals("")) &&
                retry <= 5) {

                System.err.print(br.getString(
                    BrokerResources.M_ENTER_KEY_PWD,
                    getKeystoreLocation()));
                    System.err.flush();

                if (Broker.getBroker().background) {
                    // We're running in the background and can't
                    // read the password. We still prompt for it
                    // so it's more obvious what's going on
                    // (instead of just silently failing)
                    // See 4451214
                    System.err.print("\n");
                    break;
                }

                pass_phrase = pw.getPassword();
    
                // Limit the number of times we try reading the passwd.
                // If the VM is run in the background the readLine()
                // will always return null and we'd get stuck
                // in the loop
                retry++;
            }
        }
	
	return (pass_phrase);
    }

    public static final String KEYSTORE_FILE = "javax.net.ssl.keyStore";
    public static final String KEYSTORE_PASSWORD = "javax.net.ssl.keyStorePassword";
    public static final String KEYSTORE_TYPE = "javax.net.ssl.keyStoreType";

    public static final String TRUSTSTORE_FILE = "javax.net.ssl.trustStore";
    public static final String TRUSTSTORE_PASSWORD = "javax.net.ssl.trustStorePassword";
    public static final String TRUSTSTORE_TYPE = "javax.net.ssl.trustStoreType";

    public static final String KEYSTORE_ALGORITHM = "ssl.KeyManagerFactory.algorithm";
    public static final String TRUSTSTORE_ALGORITHM = "ssl.TrustManagerFactory.algorithm";
    public static final String SECURESOCKET_PROTOCOL = "securesocket.protocol";

    public String mapSSLProperty(String prop) {
        return prop;
    }

    /**
     * Get default SSLContext configuration properties
     */
    public static Properties getDefaultSSLContextConfig(String caller,
                                                        SSLPropertyMap pm) 
                                                        throws Exception {
        if (pm == null) pm = new KeystoreUtil();

        Properties props = new Properties();
        String keystoreloc = getKeystoreLocation();
        File kf = new File(keystoreloc);
        if (!kf.exists()) {
            throw new IOException(Globals.getBrokerResources().getKString(
                                  BrokerResources.E_KEYSTORE_NOT_EXIST, keystoreloc));
        }
        props.setProperty(pm.mapSSLProperty(KEYSTORE_FILE), keystoreloc);
        props.setProperty(pm.mapSSLProperty(TRUSTSTORE_FILE), keystoreloc);

        String keystorepwd = getKeystorePassword();
        if (keystorepwd == null) {
            throw new IOException(Globals.getBrokerResources().getKString(
                                  BrokerResources.E_PASS_PHRASE_NULL));
        }
        props.setProperty(pm.mapSSLProperty(KEYSTORE_PASSWORD), keystorepwd);
        props.setProperty(pm.mapSSLProperty(TRUSTSTORE_PASSWORD), keystorepwd);
        props.setProperty(pm.mapSSLProperty(KEYSTORE_TYPE), "JKS");
        props.setProperty(pm.mapSSLProperty(TRUSTSTORE_TYPE), "JKS");

        String alg = "SunX509"; 
        try {
             KeyManagerFactory.getInstance("SunX509");
        } catch (NoSuchAlgorithmException e) {
            alg = KeyManagerFactory.getDefaultAlgorithm();
            Globals.getLogger().log(Globals.getLogger().INFO, caller+":"+e.getMessage()+
                                ", use default KeyManagerFactory algorithm "+alg);
        }
        props.setProperty(pm.mapSSLProperty(KEYSTORE_ALGORITHM), alg);

        alg = "SunX509";
        try {
             TrustManagerFactory.getInstance("SunX509");
        } catch (NoSuchAlgorithmException e) {
             alg = TrustManagerFactory.getDefaultAlgorithm();
             Globals.getLogger().log(Globals.getLogger().INFO, caller+":"+e.getMessage()+
                                 ", use default TrustManagerFactory algorithm "+alg);
        }
        props.setProperty(pm.mapSSLProperty(TRUSTSTORE_ALGORITHM), alg);

        props.setProperty(pm.mapSSLProperty(SECURESOCKET_PROTOCOL), "TLS");

        return props;
    }

    public static SSLContext getDefaultSSLContext(String caller, 
                                                  boolean trustAll) 
                                                  throws Exception {

        String keystoreloc = getKeystoreLocation();
        File kf = new File(keystoreloc);
        if (!kf.exists()) {
            throw new IOException(Globals.getBrokerResources().getKString(
                                  BrokerResources.E_KEYSTORE_NOT_EXIST, keystoreloc));
        }
        String keystorepwd = getKeystorePassword();
        if (keystorepwd == null) {
            throw new IOException(Globals.getBrokerResources().getKString(
                                  BrokerResources.E_PASS_PHRASE_NULL));
        }
        char[] keystorepwdc = keystorepwd.toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream(keystoreloc), keystorepwdc);

        KeyManagerFactory kmf = null;
        try {
             kmf = KeyManagerFactory.getInstance("SunX509");
        } catch (NoSuchAlgorithmException e) {
            String alg = KeyManagerFactory.getDefaultAlgorithm();
            Globals.getLogger().log(Globals.getLogger().INFO, caller+":"+e.getMessage()+
                                    ", use default KeyManagerFactory algorithm "+alg);
            kmf = KeyManagerFactory.getInstance(alg);
        }
        kmf.init(ks, keystorepwdc);

        TrustManager[] tm = null;
        if (!trustAll) {
            TrustManagerFactory tmf = null;
            try {
                tmf = TrustManagerFactory.getInstance("SunX509");
            } catch (NoSuchAlgorithmException e) {
                String alg = TrustManagerFactory.getDefaultAlgorithm();
                Globals.getLogger().log(Globals.getLogger().INFO, caller+":"+e.getMessage()+
                                    ", use default TrustManagerFactory algorithm "+alg);
                tmf = TrustManagerFactory.getInstance(alg);
            }
            tmf.init(ks);
            tm = tmf.getTrustManagers();
        } else {
            tm = new TrustManager[1];
            tm[0] = new DefaultTrustManager();
        }

       SSLContext ctx = SSLContext.getInstance("TLS");
       SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
       ctx.init(kmf.getKeyManagers(), tm, random);

       return ctx;
    }
}
