/*
 * X509CertificateBuilder.java
 *
 * Created on 26 February 2004, 11:13
 */

package org.bodington.server.realm;


import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.math.BigInteger;
import java.util.Date;
import java.util.logging.*;

import javax.security.auth.x500.X500Principal;
import sun.security.util.*;
import sun.security.x509.*;

import org.bodington.util.Base64Decoder;

/**
 *
 * @author  jrm
 */
public class X509CertificateBuilder
{
    String ca_keystore_path;
    CertificateFactory certificate_factory = null;
    X509Certificate x509certificate_local_ca=null;
    String subject_name_local_ca=null;
    PrivateKey private_key_local_ca=null;
    
    Base64Decoder base64decoder = new Base64Decoder();
    
    /** Creates a new instance of X509CertificateBuilder */
    public X509CertificateBuilder( String ca_keystore_path )
    {
        this.ca_keystore_path = ca_keystore_path;
    }
    
    public void init()
    {
        // needs to load CA signing certificate
        InputStream ksStream;
        try
        {
            Logger.getLogger( "org.bodington" ).fine( "Keystore: " + ca_keystore_path );
            File ksfile = new File( ca_keystore_path );
            if ( ksfile.exists() && ksfile.length() == 0L )
                return;
            ksStream = new FileInputStream( ksfile );
            Logger.getLogger( "org.bodington" ).fine( "Opened keystore." );
        }
        catch(FileNotFoundException filenotfoundexception)
        {
            // silently return - no certificates to load
            return;
        }
        try
        {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load( ksStream, "notverysecret".toCharArray() );
            ksStream.close();

            Logger.getLogger( "org.bodington" ).fine( "Loaded keystore." );
            
            String s = "local_ca";
            if(keyStore.containsAlias(s))
            {
                Logger.getLogger( "org.bodington" ).fine( "Keystore has local_ca." );
                x509certificate_local_ca = (X509Certificate)keyStore.getCertificate(s);
                if( x509certificate_local_ca == null )
                    throw new IllegalArgumentException( "Can't find local ca certificate." );
                private_key_local_ca = (PrivateKey)keyStore.getKey(s, "notverysecret".toCharArray() );
                subject_name_local_ca = x509certificate_local_ca.getSubjectX500Principal().getName();
                Logger.getLogger( "org.bodington" ).fine( "subject_name_local_ca=" + subject_name_local_ca );
            }

            certificate_factory = CertificateFactory.getInstance("X509");
            return;
        }
        catch ( java.security.GeneralSecurityException se )
        {se.printStackTrace();}
        catch ( IOException ioe )
        {ioe.printStackTrace();}
        
        throw new IllegalArgumentException( "Technical problem reading key store." );
    }
    
    private X500Signer getSigner()
        throws InvalidKeyException, NoSuchAlgorithmException, IOException
    {
        java.security.Principal p = x509certificate_local_ca.getSubjectDN();
        X500Name x500name = new X500Name( p.getName() );
        Signature signature = Signature.getInstance("SHA1WithDSA");
        signature.initSign( private_key_local_ca );
        return new X500Signer( signature, x500name );
    }

    
    
    private X509Certificate createClientCertificate( X500Name x500name, long l, int serial, PublicKey public_key )
        throws GeneralSecurityException, IOException
    {
        X509CertImpl x509certimpl;
        X500Signer x500signer = getSigner();
        Date date = new Date();
        Date date1 = new Date();
        date1.setTime(date1.getTime() + l * 1000L);
        CertificateValidity certificatevalidity = new CertificateValidity(date, date1);
        X509CertInfo x509certinfo = new X509CertInfo();
        x509certinfo.set("version", new CertificateVersion(0));
        x509certinfo.set("serialNumber", new CertificateSerialNumber(serial));
        AlgorithmId algorithmid = x500signer.getAlgorithmId();
        x509certinfo.set("algorithmID", new CertificateAlgorithmId(algorithmid));
        x509certinfo.set("subject", new CertificateSubjectName(x500name));
        x509certinfo.set("key", new CertificateX509Key(public_key));
        x509certinfo.set("validity", certificatevalidity);
        x509certinfo.set("issuer", new CertificateIssuerName(x500signer.getSigner()));
        x509certimpl = new X509CertImpl(x509certinfo);
        x509certimpl.sign( private_key_local_ca, "SHA1WithDSA" );
        return x509certimpl;
    }
    
    public X509Certificate createClientCertificate( String name, long l, int serial, String enc_pub_key )
        throws GeneralSecurityException, IOException
    {
        X500Name x500_name = new X500Name( "cn=" + name );
        
        /*
        byte[] encoded_key = base64decoder.decodeBuffer( enc_pub_key );
        X509EncodedKeySpec public_key_spec = new X509EncodedKeySpec( encoded_key );
        KeyFactory keyFactory = KeyFactory.getInstance("DSA");
        PublicKey public_key = keyFactory.generatePublic( public_key_spec );
        */

        byte[] der_keychal = base64decoder.decodeBuffer( enc_pub_key );
        PublicKey public_key = getVerifiedKey( "fixed_challenge", der_keychal );
        
        return createClientCertificate( x500_name, l, serial, public_key );
    }
    
    
    /*
     * Parse and verify the SignedPublicKeyAndChallenge data as
     * produced by KEYGEN ... return the key.
     */
    private PublicKey getVerifiedKey( String challenge, byte[] der_keychal )
    throws SignatureException, IOException,
	InvalidKeyException, NoSuchAlgorithmException
    {
	//
	// SignedPublicKeyAndChallenge ::= SEQUENCE {
	//	publicKeyAndChallenge	PublicKeyAndChallenge,
	//	signatureAlgorithm	AlgorithmIdentifier,
	//	signature		BIT STRING
	// }
	//
	DerInputStream	in = new DerInputStream( der_keychal );
	DerValue	spkac [] = in.getSequence( 3 );
	AlgorithmId	sigAlg;
	byte		sigBits [];

	if (spkac.length != 3)
	    throw new SignatureException( "invalid SPKAC" );

	sigAlg = AlgorithmId.parse( spkac[1] );
	sigBits = spkac[2].getBitString();


	//
	// PublicKeyAndChallenge ::= SEQUENCE {
	//	spki			SubjectPublicKeyInfo,
	//	challenge		IA5STRING
	// }
	//
	DerValue	pkac [];
	PublicKey		retval;

	// in = spkac [0].toDerInputStream ();
	in = new DerInputStream( spkac[0].toByteArray() );
	pkac = in.getSequence( 2 );

	if ( pkac.length != 2 )
	    throw new SignatureException ("invalid PKAC");
	if ( !challenge.equals( pkac[1].getIA5String() ) )
	    throw new SignatureException ("bad challenge in PKAC");

	retval = X509Key.parse( pkac [0] );

	//
	// Verify the signature .. shows the response was generated
	// by someone who knew the associated private key
	//
	Signature sig = Signature.getInstance( sigAlg.getName () );

	sig.initVerify (retval);
	sig.update (spkac [0].toByteArray ());
	if ( sig.verify (sigBits) )
	    return retval;
	else
	    throw new SignatureException ("bad SPKAC Signature");
    }

    
    
}
