//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.ssl;

import java.util.Iterator;
import java.util.Vector;
import java.util.Properties;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileInputStream;
import java.io.DataInputStream;

import java.security.SecureRandom;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLKeyException;

import com.dstc.security.pkcs8.PrivateKeyInfo;
import com.dstc.security.pkcs8.EncryptedPrivateKeyInfo;
import com.dstc.security.pkcs8.PKCS8EncryptedPrivateKey;
import com.dstc.security.util.Config;

/**
 * JCSI's implementation of javax.net.ssl.SSLServerSocketFactory.
 *
 * @author Ming Yung
 */
public final class SSLServerSocketFactory extends BaseSSLServerSocketFactory
{
  private static final String SSL_CONF = "jcsi.ssl.conf";
  private static final String SSL_PROPERTIES = "ssl.properties";
  private CertificateFactory certFact;
  private String keyDir = null;

  static
  {
    if (instance == null)
    {
      try
      {
        instance = new SSLServerSocketFactory();
      }
      catch (IOException e)
      {
        e.printStackTrace();
      }
    }
  }  

  public static ServerSocketFactory getDefault()
  {
    return instance;
  }

  protected SSLServerSocketFactory() throws SSLKeyException
  {
    super();

    initializeKeys();
  }

  protected void initializeKeys() throws SSLKeyException
  { 
    try
    {
      certFact = CertificateFactory.getInstance("X509");

      Properties props = Config.getProperties(SSL_CONF, SSL_PROPERTIES);
      keyDir = (String)props.get("jcsi.ssl.key.dir");

      String debugLevel = (String)props.get("jcsi.ssl.debugLevel");
      Debug.debug = Integer.parseInt(debugLevel);

      Vector suites = new Vector();
      for (int i=1; ; i++)
      {
        String suite = (String)props.get("jcsi.ssl.enabledCipherSuite." + i);
        if (suite == null)
          break;
        suites.addElement(suite);
      }
      String[] enabledCipherSuites = new String[suites.size()];
      suites.toArray(enabledCipherSuites);
      setDefaultCipherSuites(enabledCipherSuites);
        
      String req = (String)props.get("jcsi.ssl.clientAuthRequired");
      if (req.equalsIgnoreCase("true"))
        setClientAuthRequired(true);

      loadKeyAndCerts(props);
      loadTrustedCerts(props);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new SSLKeyException("server misconfiguration");
    }
  }

  private void loadKeyAndCerts(Properties props)
    throws SSLKeyException
  {
    try
    {
      String serverPrivKey = (String)props.get("jcsi.ssl.serverPrivKey");
      if (serverPrivKey == null)
        return;

      FileInputStream is 
        = new FileInputStream(keyDir + File.separator + serverPrivKey);
      byte[] encoded = new byte[is.available()];
      is.read(encoded);

      String password
        = (String)props.get("jcsi.ssl.serverPrivKey.password");

      if (password == null)
      {
        System.out.print("Enter password to unlock private key: ");
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader in = new BufferedReader(isr);
        password = in.readLine();
      }

      PKCS8EncryptedPrivateKey encKey
        = new PKCS8EncryptedPrivateKey(encoded);
      encKey.decrypt(password);
      setServerPrivateKey(encKey.getPrivateKey());

      String certs = (String)props.get("jcsi.ssl.serverCerts");
      if (certs == null)
        return;

      is = new FileInputStream(keyDir + File.separator + certs);

      DataInputStream dis = new DataInputStream(is);
      byte[] certData = new byte[dis.available()];
      dis.readFully(certData);
      is.close();

      ByteArrayInputStream bais = new ByteArrayInputStream(certData);

      Vector serverCerts = new Vector();
      while (bais.available() != 0)
      {
        X509Certificate cert
          =  (X509Certificate)certFact.generateCertificate(bais);
        serverCerts.addElement(cert);
      }
      setServerCertificates(serverCerts);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new SSLKeyException("misconfigured server keys");
    }
  }

  private void loadTrustedCerts(Properties props) throws SSLKeyException
  {
    try
    {
      String fileName = (String)props.get("jcsi.ssl.trustedCerts");
      if (fileName == null)
        return;

      File file = new File(keyDir + File.separator + fileName);
      if (!file.exists())
        file.createNewFile();

      FileInputStream is  = new FileInputStream(file);
      DataInputStream dis = new DataInputStream(is);
      byte[] certData = new byte[dis.available()];
      dis.readFully(certData);
      is.close();

      ByteArrayInputStream bais = new ByteArrayInputStream(certData);

      Vector trustedCerts = new Vector();

      while (bais.available() > 0)
      {
        X509Certificate cert =
          (X509Certificate)certFact.generateCertificate(bais);
        trustedCerts.add(cert);
      }
      setTrustedCertificates(trustedCerts);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}
