package jacorb.security;

import java.io.*;
import java.security.*;
import java.util.*;
import java.net.*;
import java.math.BigInteger;
import java.security.cert.CertificateException;

import iaik.security.rsa.*;
import iaik.x509.*;
import iaik.asn1.structures.*;
import iaik.asn1.*;
import iaik.x509.extensions.*;
import iaik.security.provider.IAIK;
import iaik.pkcs.pkcs8.*;
import iaik.utils.KeyAndCertificate;

/**
 * @author André Benvenuti ( bnv ), UGFU, Generalstab, Bern
 * @created 04.11.99
 */

public class CertificatesManager 
{

    /* -----------------05.12.98 19:16------------------------------*
     * Genarates rsa key pair and a certificate for rsa public key. *
     *                                                              *
     * It also has utility methods called by the trust deciders.    *
     *                                                              *
     * Author: André Benvenuti                                      *
     * -------------------------------------------------------------*/

    public static int saveFormat     = ASN1.PEM;
    private static final String pass_phrase = "bnv_8.12.99_passpass_find_out_ORB_JacORB1_0/beta14_SSL";
    private static Hashtable signers = new Hashtable (); // the trusted signers

    /**
     * Add a trusted signer to our signers hash table.
     *
     * @param certificate the certificate to be added as trusted
     */
    public static Principal addTrustedSigner(java.security.cert.X509Certificate certificate)
    {

	if ( certificate == null ) {
	    System.out.println( "certificate is empty." );
	    System.exit ( 0 );
	}
	Principal dn = certificate.getSubjectDN ();
	if ( dn == null ) {
	    System.out.println( "Unable to get dn from certificate." );
	    System.exit ( 0 );
	}
	Vector certs = ( Vector ) signers.get ( dn );
	if( certs == null ) {
	    certs = new Vector( 4 );
	    signers.put( dn, certs );
	}
	certs.addElement( certificate );
	if ( dn == null ) {
	    System.out.println( "Unable to add dn." );
	    System.exit ( 0 );
	}
	return dn;
    }

    /*
     * Set the list with the certificates of the trusted signers.
     * Previous trusted signers are deleted.
     *
     * @param certificateList the list of certificates
     */
    public static void setTrustedSigners ( X509Certificate[] certificateList ) 
    {
	signers = new Hashtable ();
	for ( int i = 0; i < certificateList.length; i++ ) 
	{
	    addTrustedSigner ( certificateList[ i ] );
	}
    }

    /*
     * This method checks every certificate in the chain if it was signed
     * from an signer in our trusted signers list. This method also verifies
     * the chain from the client certificate to the certificate signed by a
     * trusted signer.
     */
    public static boolean hasTrustedRoot (
					  java.security.cert.X509Certificate [] certificateChain
					  )
    {
	try {
	    // check all certificates for a trusted root
	    for ( int i = 0; i < certificateChain.length; i++ )
	    {
		if ( i > 0 ) {
		    // we are looking for a trusted root for a certificate in the chain
		    certificateChain[ i - 1 ].verify (
						      certificateChain[ i ].getPublicKey ()
						      );
		}
		Vector certs = ( Vector )signers.get(
						     certificateChain[ i ].getSubjectDN ()
						     );
		if ( certs == null ) {
		    continue;
		}
		for( int j = 0; j < certs.size (); j++ ) {
		    X509Certificate currentCert =
			( X509Certificate )certs.elementAt ( j );
		    if ( currentCert.equals ( certificateChain[ i ] )) {
			return true;
		    }
		}
	    }
	} catch ( Exception ex ) { return false; }
	return false;
    }

    /*
     * Encrypt the private key with a password according to PKCS#5 and PKCS#8.
     * We encode a value of type PrivateKeyInfo keyPair.getPrivate()
     * according to PKCS#8 to represent the private key in an
     * algorithm-independent manner, which subsequently will be encrypted
     * using the PbeWithMD5AndDES_CBC ( PKCS#5 ) algorithm and encoded
     * as PKCS#8 and save the key and the certificate chain to a file.
     */
    public static void saveKeyAndCert ( KeyPair keyPair,
					X509Certificate[] chain,
					String fileName
					) throws IOException
    {

	EncryptedPrivateKeyInfo epki = new
	    EncryptedPrivateKeyInfo(( PrivateKeyInfo )keyPair.getPrivate ());
	try {
	    epki.encrypt ( pass_phrase,
			   AlgorithmID.pbeWithMD5AndDES_CBC,  // PKCS#5 standard
			   null
			   );
	} catch ( NoSuchAlgorithmException ex ) {
	    throw new RuntimeException( "No implementation for pbeWithMD5AndDES_CBC!" );
	}
	// append the correct extension
	fileName = fileName + ( saveFormat == ASN1.DER ? ".der" : ".pem" );
	System.out.println( "save private key and certificate chain to file "
			    + fileName
			    + "..."
			    );
	new KeyAndCertificate( epki, chain ).saveTo( fileName, saveFormat );
    }


    /**
     * Generate a KeyPair.
     *
     * @param bits the length of the key ( modulus ) in bits
     * @return the KeyPair
     */
    public static KeyPair generateKeyPair( String algorithm,
					   int bits
					   )
    {
	try {
	    KeyPairGenerator generator =
		KeyPairGenerator.getInstance( algorithm, "IAIK" );
	    generator.initialize( bits );
	    KeyPair kp = generator.generateKeyPair ();
	    return kp;
	} catch ( NoSuchProviderException ex ) {
	    System.out.println ( "Provider IAIK not found!" );
	    return null;
	} catch ( NoSuchAlgorithmException ex ) {
	    System.out.println ( "Algorithm " + algorithm + " not found!" );
	    return null;
	}
    }

    /*
     * Verifies a certificate chain.
     *
     * The certificate of the user is the first one in the list
     * ( chain[0] ), and the top level certificate is the last one.
     * chain[0] = user certificate.
     * chain[x] = self signed CA certificate
     *
     * @chain the certificate chain to verify
     * @return true if the certificate chain is o.k., false otherwise
     */
    public static boolean verifyCertificateChain(
						 java.security.cert.X509Certificate[] chain
						 )
    {
	int len = chain.length;
	try {
	    chain[ len - 1 ].verify( chain [ len - 1  ].getPublicKey ());
	    for ( int i = len - 1; i > 0; i-- )
		chain[ i - 1 ].verify( chain[ i ].getPublicKey ());
	} catch ( Exception ex ) { return false; }
	return hasTrustedRoot ( chain );
    }

    /**
     * Create a certificate.
     */
    public static X509Certificate createCertificate( Name subject,
						     PublicKey pk,
						     Name issuer,
						     PrivateKey sk,
						     AlgorithmID algorithm
						     )
    {

	X509Certificate cert = new X509Certificate ();

	try {
	    cert.setSerialNumber( new BigInteger( 20, new Random ()));
	    cert.setSubjectDN( subject );
	    cert.setPublicKey( pk );
	    cert.setIssuerDN( issuer );

	    // not before yesterday
	    GregorianCalendar date = new GregorianCalendar ();
	    date.add( Calendar.DATE, -1 );
	    cert.setValidNotBefore( date.getTime ());

	    // valid six monthe
	    date.add( Calendar.MONTH, 6 );
	    cert.setValidNotAfter( date.getTime ());

	    cert.sign( algorithm, sk );
	} catch ( CertificateException ex ) {
	    System.out.println( "CertificateException: " + ex.getMessage ());
	    return null;
	} catch ( InvalidKeyException ex ) {
	    System.out.println( "InvalidKeyException: " + ex.getMessage ());
	    return null;
	} catch ( NoSuchAlgorithmException ex ) {
	    System.out.println( "NoSuchAlgorithmException: " + ex.getMessage ());
	    return null;
	}

	return cert;
    }

    /**
     * Creates the server and client certificates.
     */
    public static void main( String arg[] ) throws IOException
    {
	boolean           caFound    = false;
	boolean           selfSigned = false;
	String            userName   = null;
	String            caName     = null;
	KeyAndCertificate caKAC;
	PrivateKey        caRSA      = null;
	X509Certificate   caCert     = null;
	X509Certificate   cert;
	KeyPair           clientRSA;
	KeyPair           serverRSA;
	iaik.pkcs.pkcs8.EncryptedPrivateKeyInfo epki;

	if ( arg.length == 3 ) {
	    caFound = true;
	    if ( arg[ 0 ].equals ( "-ca" )) {
		caName = arg[ 1 ]; userName = arg[ 2 ];
	    } else if ( arg[ 1 ].equals ( "-ca" )) {
		System.out.println ( "caFound at 1" );
		caName = arg[ 2 ]; userName = arg[ 0 ];
	    } else {
		System.out.println ( "caNotFound" );
		System.out.println ( "Usage: CerttificatesManager [-ca caName] userName" );
		return;
	    }
	} else if ( arg.length != 1 ) {
	    System.out.println ( "bad # params" );
	    System.out.println ( "Usage: CertificatesManager [-ca caName] userName" );
	    return;
	} else {
	    userName = arg[ 0 ];
	    selfSigned = true;
	}

	try {
	    IAIK provider = new IAIK ();
	    Security.addProvider ( provider );

	    File certsDir         = new File( "certs" );
	    String caKeyAndCertFileName = null;
	    if ( certsDir.exists ()) {
		if ( caFound ) {
		    caKeyAndCertFileName  = "certs/"
			+ caName
			+ "KeyAndCert"
			+ ".pem";
		    caKAC  = new KeyAndCertificate ( caKeyAndCertFileName );
		    epki   = ( EncryptedPrivateKeyInfo ) caKAC.getPrivateKey ();
		    epki.decrypt ( pass_phrase );
		    caRSA  = epki.getPrivateKeyInfo ();
		    caCert = caKAC.getCertificateChain ()[ 0 ];
		}
	    } else if ( caName  != null ) {
		System.out.println ( "No certificate file "
				     + caKeyAndCertFileName
				     );
		return;
	    } else certsDir.mkdir();

	    // enquire local host name for user
	    BufferedReader reader = new
		BufferedReader ( new InputStreamReader ( System.in ));
	    String line;
	    String localhost =  InetAddress.getLocalHost().getHostName ();
	    System.out.print( "Give server host name [" + localhost + "]:");
	    line = reader.readLine();
	    if ( line.length () != 0 ) {
		localhost = line;
	    }

	    X509Certificate[] chain;

	    if ( !selfSigned && caFound ) {
		System.out.println ( "Creating server certificates for "
				     + userName + " at " + localhost + "\n"
				     );
		// First create the private keys
		clientRSA = generateKeyPair ( "RSA", 1024 );
		serverRSA = generateKeyPair ( "RSA", 1024 );

		Name issuer = new Name();
		issuer.addRDN ( ObjectID.country,             "CH" );
		issuer.addRDN ( ObjectID.organization ,       "EPFL" );
		issuer.addRDN ( ObjectID.organizationalUnit , "SSLCertificateManager" );
		issuer.addRDN ( ObjectID.commonName ,         caName );

		Name serverSubject = new Name();
		serverSubject.addRDN ( ObjectID.country,       "CH");
		serverSubject.addRDN ( ObjectID.organization , "EPFL");
		serverSubject.addRDN ( ObjectID.commonName , userName
				       + localhost
				       );
		// create server certificates
		chain = new X509Certificate[ 2 ];
		serverSubject.addRDN ( ObjectID.organizationalUnit,
				       "RSA Server Certificate"
				       );
		chain[ 0 ] = createCertificate ( serverSubject,
						 serverRSA.getPublic(),
						 issuer,
						 caRSA,
						 AlgorithmID.md5WithRSAEncryption
						 );
		chain[ 1 ] = caCert;
		verifyCertificateChain( chain );
		saveKeyAndCert ( serverRSA, chain,
				 "certs/server" + userName + "KeyAndCert"
				 );

		System.out.println ( "Creating client test certificates for "
				     + userName + " at " + localhost + "\n"
				     );
		Name clientSubject = new Name();
		clientSubject.addRDN ( ObjectID.country,       "CH");
		clientSubject.addRDN ( ObjectID.organization , "EPFL");
		clientSubject.addRDN ( ObjectID.commonName ,   "Client"
				       + userName
				       + localhost
				       );
		// create client certificates
		chain = new X509Certificate[ 2 ];
		clientSubject.addRDN ( ObjectID.organizationalUnit,
				       "RSA Client Certificate"
				       );
		chain[ 0 ] = createCertificate ( clientSubject,
						 clientRSA.getPublic(),
						 issuer,
						 caRSA,
						 AlgorithmID.md5WithRSAEncryption
						 );
		chain[ 1 ] = caCert;
		verifyCertificateChain( chain );
		saveKeyAndCert ( clientRSA, chain,
				 "certs/client" + userName + "KeyAndCert"
				 );

	    } else {
		//
		// create self signed CA certs
		//

		X509Certificate   userCert = null;
		chain                      = new X509Certificate[ 1 ];

		System.out.println ( "create self signed RSA CA certificate..."
				     + " for " + userName
				     );
		Name user = new Name();
		user.addRDN ( ObjectID.country,             "CH" );
		user.addRDN ( ObjectID.organization ,       "EPFL" );
		user.addRDN ( ObjectID.organizationalUnit , "SSLCertificateManager" );
		user.addRDN ( ObjectID.commonName ,         userName );

		KeyPair userRSA  = generateKeyPair ( "RSA", 1024 );
		userCert = createCertificate( user,
					      userRSA.getPublic(),
					      user,
					      userRSA.getPrivate(),
					      AlgorithmID.md5WithRSAEncryption
					      );
		chain[ 0 ] = userCert;
		OutputStream certFile = new FileOutputStream( "certs/"
							      + userName
							      + "Cert.pem"
							      );
		userCert.writeTo ( certFile ); certFile.close ();
		saveKeyAndCert( userRSA,
				chain, "certs/"
				+ userName
				+ "KeyAndCert"
				);
	    }

	    System.out.println("\nServer and Client certificates created.");

	} catch ( NoSuchAlgorithmException ex ) {
	    System.out.println( "NoSuchAlgorithmException: "+ex.toString());
	} catch ( UnknownHostException ex ) {
	    System.out.print( "UnknownHostException: " + ex.getMessage ());
	} catch ( CertificateException ex ) {
	    System.out.print( "CertificateException: " + ex.getMessage ());
	} catch (Exception ex) {
	    System.out.println( "Other Exception: " + ex);
	}

	System.in.read();
    }
}
