/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * This software is the proprietary information of Sun Microsystems, Inc.
 * Use is subject to license terms.
 *
 * Copyright 2004 Sun Microsystems, Inc.  Tous droits réservés.
 * Ce logiciel est propriété de Sun Microsystems, Inc.
 * Distribué par des licences qui en restreignent l'utilisation.
 *
 * ident        "@(#)MfJMXConnectorFactory.java 1.4     04/05/27 SMI"
 *
 */

/*
 * MfJMXConnectorFactory.java
 *
 * Created on May 13, 2004, 10:33 AM
 */

package com.sun.mfwk.security.factory;

/**
 *
 * @author  ooudghir
 */

import java.util.*;
import java.io.*;
import java.security.*;
import java.security.Security;
import java.security.cert.CertificateException;
import javax.net.ssl.*;
import javax.security.cert.X509Certificate;
import javax.management.remote.*;
import java.util.logging.*;
import com.sun.mfwk.util.log.MfLogService;
import com.sun.mfwk.config.MfConfig;

/** Factory to create secure JMX connector for MFWK.
 * Only JMXMP connector supported for the moment.
 */
public class MfJMXConnectorFactory {
    /** Mfwk logger. */
    protected static Logger logger = MfLogService.getLogger("MfJMXConnectorFactory");
    /** String containing the class name. */
    protected static String myClass = "com.sun.mfwk.security.factory.MfJMXConnectorFactory";
    /** MfConfig reference to retrieve properties. */
    protected static MfConfig myConfig = MfConfig.getConfig();
    
    /** There are no instances of this class */
    private MfJMXConnectorFactory() {
    }
    
    /** Creates a JMX connector (only JMXMP for the moment) and connect it using the url
     * parameter.
     * @param serviceURL URL to connect the connector. MUST be a valid jmxmp url.
     * @throws MfSecurityException This is a global exception indicating a security issue.
     * The possible causes are:<br>
     * - protocol not supported (only JMXMP for the moment)<br>
     * - cannot access the agent's keystore or password<br>
     * - cannot access the agent's truststore or password<br>
     * - IO error accessing different security files<br>
     * - cannot create the SSL socket<br>
     * @throws IOException if the connector cannot be created or the connexion cannot be made.
     * @return a JMXConnector whose connect method has been called.
     */
    public static JMXConnector connect(JMXServiceURL serviceURL)
    throws MfSecurityException, IOException {
        
        logger.entering(myClass, "connect", serviceURL);
        char keystorepass[] = myConfig.getKeystorePassword();
        if (keystorepass == null) {
            String msg = "cannot get password";
            logger.severe(msg);
            throw new NullPointerException(msg);
        }
        
        logger.info("connect: password is "+String.copyValueOf(keystorepass));
        // sanity check
        if (serviceURL == null) {
            String msg = "url cannot be null";
            logger.severe(msg);
            throw new NullPointerException(msg);
        }
        
        // Only jmxmp supported
        String proto = serviceURL.getProtocol();
        if (! proto.equals("jmxmp")) {
            String msg = "Protocol not supported: "+proto;
            logger.severe(msg);
            throw new MfSecurityException(msg);
        }
        // Retrieving properties
        String kspath = myConfig.getProperty("mfwk.agent.security.keystore");
        if (kspath == null) { // Should not occur
            String msg = "cannot get property mfwk.agent.security.keystore.";
            logger.severe(msg);
            throw (new MfSecurityException(msg));
        }
        String tspath = myConfig.getProperty("mfwk.security.truststore");
        if (tspath == null)  { // Should not occur
            String msg = "cannot get property mfwk.security.truststore.";
            logger.severe(msg);
            throw (new MfSecurityException(msg));
        }
        char truststorepass[] = myConfig.getProperty("mfwk.security.trustpass").toCharArray();
        if (truststorepass == null) { // Should not occur
            String msg = "cannot get property mfwk.security.trustpass.";
            logger.severe(msg);
            throw (new MfSecurityException(msg));
        }
        
        KeyStore ks = null;
        KeyManagerFactory kmf = null;
        try {
            ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream(kspath), keystorepass);
            kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, keystorepass);
        }  catch (Exception e) {
            String msg = null;
            if (e instanceof KeyStoreException)
                msg = "Cannot get keystore instance : ";
            else if (e instanceof IOException)
                msg = "Cannot load keystore from file "+kspath+" : ";
            else if (e instanceof NoSuchAlgorithmException)
                msg = "Cannot get KeyManagerFactory instance with algorithm SUNX509 :";
            else if (e instanceof CertificateException)
                msg = "Cannot load keystore :";
            else if (e instanceof UnrecoverableKeyException)
                msg = "Cannot init KeyManagerFactory : ";
            else
                msg = "Unknown exception : ";
            logger.warning("MfSecurityException "+msg+" : "+e.getMessage());
            throw new MfSecurityException(msg);
        }
        
        KeyStore ts = null;
        TrustManagerFactory tmf = null;
        try {
            ts = KeyStore.getInstance("JKS");
            ts.load(new FileInputStream(tspath), truststorepass);
            tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ts);
        } catch (Exception e) {
            String msg = null;
            if (e instanceof KeyStoreException)
                msg = "Cannot get keystore instance : ";
            else if (e instanceof IOException)
                msg = "Cannot load keystore from file "+tspath+" : ";
            else if (e instanceof NoSuchAlgorithmException)
                msg = "Cannot get KeyManagerFactory instance with algorithm SUNX509 :";
            else if (e instanceof CertificateException)
                msg = "Cannot load keystore :";
            else
                msg = "Unknown exception : ";
            logger.warning("MfSecurityException "+msg+" : "+e.getMessage());
            throw new MfSecurityException(msg);
        }
        
        SSLSocketFactory ssf = null;
        try {
            SSLContext ctx = SSLContext.getInstance("TLSv1");
            ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            ssf = ctx.getSocketFactory();
        } catch (Exception e) {
            String msg = null;
            if (e instanceof NoSuchAlgorithmException)
                msg = "cannot get instance of SSLContext with algorithm TLSv1";
            else if (e instanceof KeyManagementException)
                msg = "Cannot init SSL context";
            else
                msg = "Unknown exception : ";
            logger.warning("MfSecurityException "+msg+" : "+e.getMessage());
            throw new MfSecurityException(msg);
        }
        
        HashMap env = new HashMap();
        env.put("jmx.remote.profiles", "TLS");
        env.put("jmx.remote.tls.socket.factory", ssf);
        env.put("jmx.remote.tls.enabled.protocols", "TLSv1");
        env.put("jmx.remote.tls.enabled.cipher.suites","SSL_RSA_WITH_NULL_MD5");
        
        
        JMXConnector jmxc;
        try {
            jmxc = JMXConnectorFactory.connect(serviceURL, env);
        } catch (Exception e) {
            String msg = "connection failed";
            logger.severe("MfSecurityException "+msg +" : "+e.getMessage());
            throw new MfSecurityException(msg);
        }
        logger.exiting(myClass, "connect");
        return(jmxc);
    }
    
}
