//
// JSSESocketProvider.java
//
// The contents of this file are subject to the SNIA Public License Version 1.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.snia.org/resources/openSource.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is JSSESocketProvider.java
// 
// The Initial Developer of the Original Code is Viktor Mihajlovski.
//
// Contributor(s):
//
//
// This source uses the Solaris WBEM APIs version 2.2. 
// Synchronization will be maintained with the Solaris WBEM APIS,
// which are being standardized through the Java Community Process.
//
//
// $Id: JSSESocketProvider.java,v 1.1 2003/07/17 18:32:55 kumpf Exp $
//
// Revision 1.4 08/16/2002 ( Bapu Patil)
// Added certificate trust manager support
//
//
// $Log: JSSESocketProvider.java,v $
// Revision 1.1  2003/07/17 18:32:55  kumpf
// HP-BAPU: PEP-64 CIM Client and CIM Listener Java Interface
//
// Revision 1.1.1.1  2001/10/31 18:17:21  karl
// import of SNIA 0_7
//
// Revision 1.1  2001/06/23 11:31:37  davidsi
// - Support for SSL.
//
// Revision 1.3  2000/12/18 12:42:40  mihajlov
// Changed to use an explicitly created SSLContext object, allowing for
// faster random seed operation.
//
// Revision 1.2  2000/12/13 13:18:12  mihajlov
// adapted to SocketProvider.
//
// Revision 1.1  2000/12/13 09:36:59  mihajlov
// Initial revision
//


package org.snia.wbemcmd.xml;

import java.util.Enumeration;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import org.snia.wbemcmd.utils.Log;
import org.snia.wbemcfg.GlobalConfig;
import org.snia.wbem.cim.CIMException;


/**
   JSSESocketProvider provides SSL based socket connection
   to CIM Server and Clients. This class depends on JSSE implementation 
   <p> 
   When using SSL connection, you may specify JSSE KeyStore, KeyStore Password,
   and TrustStore. This can done either using System.setProperty()  or by 
   passing as a command-line argument.
   </p> 
   <p> 
   This class also provides an interface for clients and server to specify
   it's own Certitification TrustManager to validate certificates. if
   specified your own TrustManager it will override JSSE X509 TrustManager.
   </p> 

   @ see javax.net.ssl.SSLSocketFactory
   @ see javax.net.ssl.SSLServerSocketFactory
*/
public class JSSESocketProvider extends SocketProvider 
{
    private static SSLSocketFactory       sf;
    private static SSLServerSocketFactory ssf;
    private static boolean isValid = true;
    private static boolean isValidTM = true;

    static 
    {
        // We do not use the (default) internal SSLContext because
        // the seeding of the SecureRandom object takes forever.
        // This requires however using the com.sun.net.ssl.* classes
        // which makes us JSSE implementation dependent for now.
        try 
        {
            // Retrieve system properties for keystore
            String ksName = System.getProperty("javax.net.ssl.keyStore");
            String ksPw = System.getProperty("javax.net.ssl.keyStorePassword");

            //
            // Attn: 08/16/2002 ( Bapu Patil)
            // Do we have trust manager property defined in cim.properties 
            //
            TrustManager[] tm = null;
            String trustModule = org.snia.wbemcfg.GlobalConfig.TrustManager;
            boolean isTrustModule=false;

            if ( trustModule != null )
            {
                try 
                {
                    Class cp=Class.forName(trustModule);
                    if (cp==null) 
                    {
                        Log.println("Failed to load class: " + trustModule);
                    }

                    X509TrustManager myCertMgr =(X509TrustManager)cp.newInstance();
                    tm = new TrustManager[] {myCertMgr};
                    isTrustModule=true;
                }
                catch ( Exception nie) 
                { 
                    Log.println("Unable to initialize trust module -" + trustModule); 
                    isValidTM = false;
                }
            }

            // Get SSL context and secure random object
            SSLContext sc = SSLContext.getInstance("TLS");

            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
            sr.setSeed(System.currentTimeMillis());

            if (ksName != null && ksName.length() != 0) 
            {
                // keystore specified - load it
                KeyStore ks = KeyStore.getInstance("JKS");
                ks.load(new FileInputStream(ksName), ksPw.toCharArray());

                javax.net.ssl.KeyManagerFactory kmf = 
                javax.net.ssl.KeyManagerFactory.getInstance("SunX509");

                kmf.init(ks, ksPw.toCharArray());

                if ( isTrustModule == false )
                    sc.init(kmf.getKeyManagers(),null,sr);
                else
                    sc.init(kmf.getKeyManagers(), tm, sr);  
            } 
            else 
            {
                //
                // no keystore specified
                //
                if  ( isTrustModule == false )
                    sc.init(null,null,sr);
                else
                    sc.init(null, tm, sr); 
            }

            // Get SSL socket factories
            sf = sc.getSocketFactory();
            ssf = sc.getServerSocketFactory();
        } 
        //ATTN: HP-Nag --> catch all exceptions here
        catch (Exception exc) 
        {
            //
            // ATTN: record detailed failure reasons.
            //
            Log.println("");
	    exc.printStackTrace();

            isValid = false;
        }
    } 
    
    void assertValidity() throws IOException, CannotCreateSocketException 
    {
        //
        // TrustManager initialization failed.
        //
        if (!isValidTM )
        {
            if (GlobalConfig.DEBUG_XML)
            {
                Log.println("JSSESocketProvider: assertValidity()-" +
                    " Unable to Initialize Specified TrustManager");
            }

            throw new CannotCreateSocketException(
                CIMException.CIMCLIENT_ERR_SSL_HANDSHAKE_FAILED,
                    " Unable to Initialize Specified TrustManager.");
        }

        //
        // SSL certificate validation failed.
        //
        if (!isValid)
        {
            if (GlobalConfig.DEBUG_XML)
            {
                Log.println("JSSESocketProvider: assertValidity()-" +
                    " SSL Socket Factory Initialization Failed");
            }

            throw new CannotCreateSocketException(
                CIMException.CIMCLIENT_ERR_SSL_HANDSHAKE_FAILED,
                    "SSL Socket Factory Initialization Failed:");
        }
    }

    public String getHTTPType()
    {
        return "https"; // HTTP over SSL
    }

    public Socket createSocket(InetAddress addr, int port) 
        throws IOException, CannotCreateSocketException 
    {
        assertValidity();
        return sf.createSocket(addr,port);
    }

    public ServerSocket createServerSocket(int port, int backlog) 
        throws IOException, CannotCreateSocketException
    {
        assertValidity();       
        // Pre code: TODO- REMOVE
        //return ssf.createServerSocket(port,backlog);
        //

        // added for 2.0  by Bapu
        SSLServerSocket ss = (SSLServerSocket)ssf.createServerSocket(port,backlog);

        // set the defaults for SSLSockets created by the
        // server - use SSL server mode and require client
        // authentication
        //
        ss.setNeedClientAuth(true);
        return ((ServerSocket)ss);
    }
    
    public Socket createSocketWrapper(Socket s, String host,int port) 
        throws IOException, CannotCreateSocketException 
    {
        assertValidity();
        return sf.createSocket(s,host,port,true);
    }    
}
