package org.bouncycastle.jce.netscape;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;

/**
 *
 * 
 * Handles NetScape certificate request (KEYGEN), these are constructed as:
 * <pre><code>
 *   SignedPublicKeyAndChallenge ::= SEQUENCE {
 *     publicKeyAndChallenge    PublicKeyAndChallenge,
 *     signatureAlgorithm       AlgorithmIdentifier,
 *     signature                BIT STRING
 *   }
 * </pre>
 *
 * PublicKey's encoded-format has to be X.509.
 *
 **/
public class NetscapeCertRequest
    extends ASN1Encodable
{
    AlgorithmIdentifier    sigAlg;
    AlgorithmIdentifier    keyAlg;
    byte        sigBits [];
    String challenge;
    DERBitString content;
    PublicKey pubkey ;
    
    private static ASN1Sequence getReq(
        byte[]  r)
        throws IOException
    {
        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(r));

        return ASN1Sequence.getInstance(aIn.readObject());
    }

    public NetscapeCertRequest(
        byte[]  req)
        throws IOException
    {
        this(getReq(req));
    }

    public NetscapeCertRequest (ASN1Sequence spkac)
    {
        try {

            //
            // SignedPublicKeyAndChallenge ::= SEQUENCE {
            //    publicKeyAndChallenge    PublicKeyAndChallenge,
            //    signatureAlgorithm    AlgorithmIdentifier,
            //    signature        BIT STRING
            // }
            //
            if (spkac.size() != 3) {
                throw new IllegalArgumentException ("invalid SPKAC (size):"+
                                                    spkac.size());
            }
            
            sigAlg = new AlgorithmIdentifier(((ASN1Sequence)spkac.getObjectAt(1)));
            sigBits = ((DERBitString)spkac.getObjectAt(2)).getBytes ();
            
            //
            // PublicKeyAndChallenge ::= SEQUENCE {
            //    spki            SubjectPublicKeyInfo,
            //    challenge        IA5STRING
            // }
            //
            ASN1Sequence    pkac = (ASN1Sequence)spkac.getObjectAt(0);

            if (pkac.size() != 2)
                throw new IllegalArgumentException ("invalid PKAC (len): "+
                                                    pkac.size());

            challenge = ((DERIA5String)pkac.getObjectAt(1)).getString ();

            //this could be dangerous, as ASN.1 decoding/encoding
            //could potentially alter the bytes
            content = new DERBitString(pkac);
                        
            SubjectPublicKeyInfo pubkeyinfo = new SubjectPublicKeyInfo((ASN1Sequence)pkac.getObjectAt(0));

            X509EncodedKeySpec xspec =
                new X509EncodedKeySpec (new DERBitString (pubkeyinfo).getBytes ());

            keyAlg = pubkeyinfo.getAlgorithmId ();
            pubkey =
                KeyFactory.getInstance(keyAlg.getObjectId().getId (),"BC").generatePublic(xspec);
            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public NetscapeCertRequest (String challenge,
                                AlgorithmIdentifier signing_alg,
                                PublicKey pub_key)
        throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException
    {

        this.challenge = challenge;
        sigAlg = signing_alg;
        pubkey = pub_key;

        ASN1EncodableVector content_der = new ASN1EncodableVector();
        content_der.add(getKeySpec());
        //content_der.add(new SubjectPublicKeyInfo(sigAlg, new RSAPublicKeyStructure(pubkey.getModulus(), pubkey.getPublicExponent()).getDERObject() ));
        content_der.add(new DERIA5String (challenge));
        
        content = new DERBitString(new DERSequence(content_der));
    }

    public String getChallenge () {
        return challenge;
    }

    public void setChallenge (String value) {
        challenge = value;
    }

    public AlgorithmIdentifier getSigningAlgorithm () {
        return sigAlg;
    }

    public void setSigningAlgorithm (AlgorithmIdentifier value) {
        sigAlg = value;
    }

    public AlgorithmIdentifier getKeyAlgorithm () {
        return keyAlg;
    }

    public void setKeyAlgorithm (AlgorithmIdentifier value) {
        keyAlg = value;
    }
    
    public PublicKey getPublicKey () {
        return pubkey;
    }

    public void setPublicKey (PublicKey value) {
        pubkey = value;
    }

    public boolean verify (String challenge)
        throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException, NoSuchProviderException
    {
        if (!challenge.equals(this.challenge)) return false;
        
        //
        // Verify the signature .. shows the response was generated
        // by someone who knew the associated private key
        //
        Signature    sig =
            Signature.getInstance (sigAlg.getObjectId ().getId(),"BC");
        sig.initVerify (pubkey);
        sig.update (content.getBytes());
        if (sig.verify (sigBits))
            return true;
        else
            return false;
        
    }

    public void sign (PrivateKey priv_key)
        throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException, NoSuchProviderException, InvalidKeySpecException
    {
        sign (priv_key, null);
    }

    public void sign (PrivateKey priv_key, SecureRandom rand)
        throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException, NoSuchProviderException, InvalidKeySpecException
    {
        Signature    sig =
            Signature.getInstance (sigAlg.getObjectId ().getId(),"BC");
        
        if (rand != null) {
            sig.initSign (priv_key, rand);
        } else {
            sig.initSign (priv_key);
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DEROutputStream deros = new DEROutputStream (baos);

        ASN1EncodableVector pkac = new ASN1EncodableVector();
        
        pkac.add(getKeySpec());
        pkac.add(new DERIA5String(challenge));

        try {
            deros.writeObject (new DERSequence(pkac));
            deros.close();
        } catch (IOException ioe) {
            throw new SignatureException (ioe.getMessage());
        }
        
        sig.update (baos.toByteArray());

        sigBits = sig.sign ();
    }

    private DERObject getKeySpec ()
        throws NoSuchAlgorithmException, InvalidKeySpecException,
            NoSuchProviderException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        DERObject obj = null;
        try {

            baos.write (pubkey.getEncoded());
            baos.close ();

            ASN1InputStream derin =
                new ASN1InputStream (new ByteArrayInputStream (baos.toByteArray()));
            
            obj = derin.readObject();
        } catch (IOException ioe) {
            throw new InvalidKeySpecException (ioe.getMessage());
        }
        return obj;
    }
    
    
    public DERObject toASN1Object()
    {
        ASN1EncodableVector spkac = new ASN1EncodableVector();
        ASN1EncodableVector pkac = new ASN1EncodableVector();

        try
        {
            pkac.add(getKeySpec());
        }
        catch (Exception e)
        {
            //ignore
        }

        pkac.add(new DERIA5String(challenge));

        spkac.add(new DERSequence(pkac));
        spkac.add(sigAlg);
        spkac.add(new DERBitString (sigBits));

        return new DERSequence(spkac);
    }
}
