//////////////////////////////////////////////////////////////////////////// 
// 
// 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.provider;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.Random;
import java.security.SignatureSpi;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.MessageDigest;
import java.security.KeyFactory;
import java.security.InvalidParameterException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import com.dstc.security.asn1.Asn1Exception;
import com.dstc.security.asn1.Sequence;
import com.dstc.security.asn1.Integer;

/**
 * <p>Implements the DSA signature algorithm following the DSS standard.
 *
 * @see java.security.Signature
 * @see com.dstc.security.provider.DSAParameters
 * @see com.dstc.security.provider.DSAPublicKey
 * @see com.dstc.security.provider.DSAPrivateKey
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public class DSA extends SignatureSpi 
{
  private DSAPublicKey pubKey;
  private DSAPrivateKey privKey;
  private ByteArrayOutputStream bos;

  protected void engineSetParameter(String param, Object value)
    throws InvalidParameterException
  {
    //Deprecated. Does nothing
  }

  protected Object engineGetParameter(String param)
    throws InvalidParameterException
  {
    //Deprecated.
    return null;
  }

  protected void engineSetParameter(AlgorithmParameterSpec params) 
    throws InvalidAlgorithmParameterException
  {
    //Does nothing
  }

  ///Initializes for verification with a public key
  protected void engineInitVerify(PublicKey publicKey) 
     throws InvalidKeyException 
  {
    if (!(publicKey.getAlgorithm().equals("DSA"))) 
    {
      throw new InvalidKeyException("Not a DSA Public Key");
    }

    try
    {
      KeyFactory keyFact = KeyFactory.getInstance("DSA", "DSTC");
      this.pubKey = (DSAPublicKey)keyFact.generatePublic(
        new X509EncodedKeySpec(publicKey.getEncoded()));
      bos = new ByteArrayOutputStream();
    } 
    catch (Exception e)
    {
      throw new InvalidKeyException(e.toString());
    }
  }

  ///Initializes for signing with a private key
  protected void engineInitSign(PrivateKey privateKey) 
     throws InvalidKeyException 
  {
    if (privateKey.getAlgorithm().equals("DSA")) 
    {
      this.privKey = (DSAPrivateKey)privateKey;
      bos = new ByteArrayOutputStream();
    } 
    else 
    {
      throw new InvalidKeyException ("Not a DSA Private Key");
    }
  }

  ///Updates buffer with a byte
  protected void engineUpdate(byte b) throws SignatureException 
  {
    byte[] temp = {b};
    engineUpdate(temp, 0, 1);
  }

  ///Updates buffer with a byte array
  protected void engineUpdate(byte b[], int off, int len) 
      throws SignatureException 
  {
    bos.write(b, off, len);
  }

  ///Signs the message in the buffer and returns the signature
  protected byte[] engineSign() throws SignatureException 
  {
    try 
    {
      java.security.interfaces.DSAParams dsaParams = privKey.getParams();
      BigInteger k = new BigInteger(dsaParams.getQ().bitLength()-1,
                                     new Random());
  
      MessageDigest md = MessageDigest.getInstance("SHA", "SUN");

      byte[] data = bos.toByteArray();
      if (data.length == 0)
        throw new SignatureException("Nothing to sign");

      BigInteger h = new BigInteger(1, md.digest(data));
      BigInteger r = dsaParams.getG().modPow(k, dsaParams.getP()).mod(
          dsaParams.getQ());

      BigInteger xrPlusH = r.multiply(privKey.getX()).add(h);
      BigInteger kinv = k.modInverse(dsaParams.getQ());
      BigInteger s = kinv.multiply(xrPlusH).mod(dsaParams.getQ());
      
      return (new DssSig(r, s)).encode();
    } 
    catch (Exception e) 
    {
      throw new SignatureException(e.getMessage());
    }
  }

  ///Verifies the signature
  protected boolean engineVerify(byte sigBytes[]) 
     throws SignatureException 
  {
    try 
    {
      DssSig dssSig = new DssSig(sigBytes);
      BigInteger r = dssSig.r;
      BigInteger s = dssSig.s;

      java.security.interfaces.DSAParams dsaParams = pubKey.getParams();
  
      BigInteger w = s.modInverse(dsaParams.getQ ());
      MessageDigest md = MessageDigest.getInstance("SHA", "SUN");

      byte[] data = bos.toByteArray();
      if (data.length == 0)
        throw new SignatureException("Nothing to verify");

      BigInteger h = new BigInteger(1, md.digest(data));
      BigInteger u1 = h.multiply(w).mod(dsaParams.getQ());

      BigInteger u2 = r.multiply(w).mod(dsaParams.getQ());
      BigInteger v1 = dsaParams.getG().modPow(u1, dsaParams.getP());
      BigInteger v2 = pubKey.getY().modPow(u2, dsaParams.getP());
      BigInteger v = v1.multiply(v2).mod(dsaParams.getP()).mod(
                     dsaParams.getQ());
      return (v.equals(r));
    } 
    catch (Exception e) 
    {
      throw new SignatureException(e.getMessage());
    }
  }

  /**
   * <p>An inner class representing the ASN.1 structure DssSig.
   *
   * <p>DssSig := SEQUENCE {r INTEGER, s INTEGER}
   */
  protected class DssSig extends com.dstc.security.asn1.Sequence 
  {
    private BigInteger r, s;

    /**
     * Constructs a DssSig from a DER encoding
     */
    protected DssSig(byte encoded[]) throws Asn1Exception
    {
      doDecode(encoded);
      this.r = ((com.dstc.security.asn1.Integer)
                 components.elementAt(0)).getBigInteger();
      this.s = ((com.dstc.security.asn1.Integer)
                 components.elementAt(1)).getBigInteger();
    }

    /**
     * Constructs a DssSig from r and s
     */
    protected DssSig(BigInteger r, BigInteger s) 
    {
      this.r = r;
      this.s = s;
      this.addComponent(new com.dstc.security.asn1.Integer(r));
      this.addComponent(new com.dstc.security.asn1.Integer(s));
    }
  }
}
